Astronomical pacing of the Lau Biogeochemical Event captured in the Kosov Quarry section

A study on the imprint of astronomical cycles in the Kosov quarry spanning the Lau Event during the Silurian

Authors

Michiel Arts1

Anne-Christine da Silva1

1 - SEDICLIM lab, Department of Geology, University of Liege, Belgium

Published

October 1, 2026

This study is based onthe induration record extracted from the litholog of Fryda, Jiri & Lehnert, Oliver & Frýdová, Barbora & Farkas, Juraj & Kubajko, Michal. (2020). Carbon and sulfur cycling during the mid-Ludfordian anomaly and the linkage with the late Silurian Lau/Kozlowskii Bioevent. Palaeogeography, Palaeoclimatology, Palaeoecology. 564. doi:10.1016/j.palaeo.2020.110152

Code
setwd("D:/Phd/documents/kosov")

library(truncnorm)
library(DescTools)
library(rlist)
library(parallel)
library(foreach)
library(iterators)
library(doParallel)
library(astrochron)
library(ggplot2)
library(tidyr)
library(doSNOW)
library(progress)
library(tcltk)
library(matrixStats)
library(tidyr)
library(colorednoise)
library(stats)
library(WaveletComp)
library(reshape2)
library(viridis)
library(WaverideR)
library(raster)
library(jpeg)
library(httr)
library(gt)
library(geoscale)
library(dplyr)
library(readr)
library(readxl)

#this function is needed to complete the wavelet tracking
completed_series <-   function (wavelet = NULL,
                                tracked_curve = NULL,
                                period_up = 1.2,
                                period_down = 0.8,
                                extrapolate = TRUE,
                                genplot = FALSE,
                                keep_editable = FALSE) {
  my.w <- wavelet
  my.data <- cbind(wavelet$x, wavelet$y)
  Pwert <- my.w$Power
  maxdetect <- matrix(nrow = (nrow(Pwert)), ncol = ncol(Pwert), 0)
  for (j in 1:ncol(Pwert)) {
    for (i in 2:(nrow(maxdetect) - 1)) {
      if ((Pwert[i, j] - Pwert[(i + 1), j] > 0) &
          (Pwert[i, j] - Pwert[(i - 1), j] > 0)) {
        maxdetect[i, j] <- 1
      }
    }
  }
  out <- tracked_curve
  out <- na.omit(out)
  yleft_out <- out[1, 2]
  yright_out <- out[nrow(out), 2]
  seq <- seq(
    from = min(my.data[, 1]),
    to = max(my.data[, 1]),
    by = my.data[, 1][2] - my.data[, 1][1]
  )
  app <- approx(
    x = out[, 1],
    y = out[, 2],
    xout = seq,
    method = "linear",
    yleft = yleft_out,
    yright = yright_out
  )
  app <- as.data.frame(cbind(app$x, app$y))
  periods <- as.data.frame(my.w$Period)
  completed_series <- matrix(data = NA,
                             nrow = nrow(app[, ]),
                             ncol = 2)
  completed_series[, 1] <- app[, 1]
  
  
  for (i in 1:nrow(completed_series)) {
    row_nr <- Closest(periods[, 1], app[i, 2], which = TRUE)
    row_nr
    row_nr <- row_nr[1]
    if (maxdetect[row_nr, i] == 1) {
      completed_series[i, 2] <- periods[row_nr, ]
    }
    else {
      sel_row <- as.data.frame(maxdetect[, i])
      sel_row$period <- periods[, 1]
      sel_row <- sel_row[sel_row[, 1] > 0, ]
      row_nr_sel_row <- Closest(sel_row[, 2], app[i, 2], which = TRUE)
      row_nr_closest <- Closest(periods[, 1], sel_row[row_nr_sel_row, 2], which = TRUE)
      closest_period <- periods[unlist(row_nr_closest[1]), 1]
      if ((closest_period < (app[i, 2] * period_up)) &
          (closest_period > (app[i, 2] * period_down))) {
        completed_series[i, 2] <- closest_period
      }
    }
  }
  if (extrapolate == TRUE) {
    completed_series <- na.omit(completed_series)
    yleft_comp <- completed_series[1, 2]
    yright_com <- completed_series[nrow(completed_series), 2]
    seq <- seq(
      from = min(my.data[, 1]),
      to = max(my.data[, 1]),
      by = my.data[, 1][2] - my.data[, 1][1]
    )
    app <- approx(
      x = completed_series[, 1],
      y = completed_series[, 2],
      xout = seq,
      method = "linear",
      yleft = yleft_comp,
      yright = yright_com
    )
    completed_series <- as.data.frame(cbind(app$x, app$y))
  }
  if (genplot == TRUE) {
    if (keep_editable == FALSE) {
      oldpar <- par(no.readonly = TRUE)
      on.exit(par(oldpar))
    }
    plot(completed_series,
         type = "l",
         col = "red")
    lines(tracked_curve, col = "black")
  }
  return(completed_series)
}
#this function is needed to do the numerical age-modelling 

curve2time_unc_anchor <- function (age_constraint = NULL, tracked_cycle_curve = NULL, 
  tracked_cycle_period = NULL, tracked_cycle_period_unc = NULL, 
  tracked_cycle_period_unc_dist = "n", n_simulations = 20, 
  gap_constraints = NULL, proxy_data = NULL, cycles_check = NULL, 
  uncer_cycles_check = NULL, max_runs = 1000, run_multicore = FALSE, 
  verbose = FALSE, genplot = FALSE, keep_nr = 2, keep_all_time_curves = FALSE, 
  dj = 1/200, lowerPeriod = 1, upperPeriod = 4600, omega_nr = 6, 
  seed_nr = 1337, dir = TRUE) 
{
dat <- as.matrix(tracked_cycle_curve[, ])
dat <- na.omit(dat)
dat <- dat[order(dat[, 1], na.last = NA, decreasing = F), ]
npts <- length(dat[, 1])
start <- dat[1, 1]
end <- dat[length(dat[, 1]), 1]
x1 <- dat[1:(npts - 1), 1]
x2 <- dat[2:(npts), 1]
dx = x2 - x1
dt = median(dx)
xout <- seq(start, end, by = dt)
npts <- length(xout)
interp <- approx(dat[, 1], dat[, 2], xout, method = "linear", n = npts)
interp_2 <- approx(dat[, 1], dat[, 3], xout, method = "linear", n = npts)
tracked_cycle_curve_2 <- cbind(interp[[1]], interp[[2]], interp_2[[2]])
out <- matrix(
  data = NA,
  nrow = nrow(tracked_cycle_curve_2),
  ncol = n_simulations
)
multi_tracked <- tracked_cycle_curve_2
age_curve <- tracked_cycle_curve_2[, c(1, 2)]
x_axis <- tracked_cycle_curve_2[, c(1)]
res_matrix <- matrix(data = NA,
                     nrow = nrow(age_curve),
                     ncol = n_simulations)
if (verbose == TRUE) {
  pb <- txtProgressBar(max = n_simulations, style = 3)
  progress <- function(n)
    setTxtProgressBar(pb, n)
  opts <- list(progress = progress)
}else {
  opts = NULL
}
set.seed(seed_nr)
if (nrow(age_constraint) > 0) {
  if (run_multicore == FALSE) {
    res_list <- list()
    for (i in 1:n_simulations) {
      check_astro_age <- matrix(
        data = NA,
        ncol = 1,
        nrow = nrow(age_constraint)
      )
      anchor_depth <- matrix(
        data = NA,
        ncol = 1,
        nrow = nrow(age_constraint)
      )
      anchor_age <- matrix(
        data = NA,
        ncol = 1,
        nrow = nrow(age_constraint)
      )
      if (keep_all_time_curves == TRUE) {
        time_curve_comb <- matrix(data = NA,
                                  ncol = 0,
                                  nrow = length(x_axis))
      }
      for (klm in 1:nrow(age_constraint)) {
        if (age_constraint[klm, 4] == "u") {
          check_astro_age[klm] <- runif(
            1,
            min = tracked_cycle_period -
              tracked_cycle_period_unc,
            max = tracked_cycle_period +
              tracked_cycle_period_unc
          )
        }
        if (age_constraint[klm, 4] == "n") {
          check_astro_age[klm] <- rnorm(1,
                                        mean = as.numeric(age_constraint[klm, 2]),
                                        sd = as.numeric(age_constraint[klm, 3]))
        }
      }
      anchor_astr <- 1
      anchor_radio <- 0
      sel_rws <- seq(from = 1,
                     to = nrow(age_constraint),
                     by = 1)
      runs <- 0
      anchor_diff <- matrix(data = NA,
                            ncol = 0,
                            nrow = length(sel_rws))
      while (anchor_astr > anchor_radio) {
        age_constraint_2 <- age_constraint[c(sel_rws), ]
        check_astro_age_2 <- check_astro_age[c(sel_rws), ]
        anchor_depth_2 <- anchor_depth[c(sel_rws), ]
        anchor_age_2 <- anchor_age[c(sel_rws), ]
        validator <- 1
        while (validator == 1) {
          new_curve <- multi_tracked[, c(1, 2)]
          val <- rnorm(1, mean = multi_tracked[1, 2], sd = multi_tracked[1, 3])
          pnorm_val <- 1 - pnorm(val,
                                 mean = multi_tracked[1, 2],
                                 sd = multi_tracked[1, 3],
                                 lower.tail = FALSE)
          for (j in 1:nrow(new_curve)) {
            new_curve[j, 2] <- 1 / (qnorm(pnorm_val, mean = multi_tracked[j, 2], sd = multi_tracked[j, 3]))
          }
          if (tracked_cycle_period_unc_dist == "u") {
            tracked_cycle_period_new <- runif(
              1,
              min = tracked_cycle_period -
                tracked_cycle_period_unc,
              max = tracked_cycle_period +
                tracked_cycle_period_unc
            )
          }
          if (tracked_cycle_period_unc_dist == "n") {
            tracked_cycle_period_new <- rnorm(1, mean = tracked_cycle_period, sd = tracked_cycle_period_unc)
          }
          time_curve <- WaverideR::curve2time(
            tracked_cycle_curve = new_curve,
            tracked_cycle_period = tracked_cycle_period_new,
            genplot = FALSE,
            keep_editable = FALSE
          )
          dif_mat <- time_curve[2:(nrow(time_curve)), 2] - time_curve[1:(nrow(time_curve) -
                                                                           1), 2]
          dif_mat_min <- min(dif_mat)
          if (dif_mat_min > 0) {
            validator <- 0
          }
        }
        if (dir == FALSE) {
          time_curve[, 2] <- max(time_curve[, 2]) -
            time_curve[, 2]
        }
        gaps_dur <- 0
        if (is.null(gap_constraints) == FALSE) {
          gaps_dur <- matrix(
            data = NA,
            nrow = nrow(gap_constraints[, ]),
            ncol = 1
          )
          for (qx in 1:nrow(gap_constraints)) {
            if (gap_constraints[qx, 4] == "u") {
              if (as.numeric(gap_constraints[qx, 1]) -
                  as.numeric(gap_constraints[qx, 2]) <
                  0) {
                a <- 0
              }
              else {
                a <- as.numeric(gap_constraints[qx, 1]) - as.numeric(gap_constraints[qx, 2])
              }
              gap_dur_new <- runif(
                1,
                min = a,
                max = as.numeric(gap_constraints[qx, 1]) + as.numeric(gap_constraints[qx, 2])
              )
            }
            if (gap_constraints[qx, 4] == "n") {
              gap_dur_new <- rnorm(
                1,
                mean = as.numeric(gap_constraints[qx, 1]),
                sd = as.numeric(gap_constraints[qx, 2])
              )
              if (gap_dur_new < 0) {
                gap_dur_new <- 0
              }
            }
            gaps_dur[qx, 1] <- gap_dur_new
            row_nr <- DescTools::Closest(time_curve[, 1],
                                         as.numeric(gap_constraints[qx, 3]),
                                         which = TRUE)
            out_3 <- time_curve[, 2]
            out_4 <- out_3[row_nr:length(out_3)]
            if (dir == FALSE) {
              out_4 <- out_4 - gap_dur_new
              time_curve[, 2] <- c(out_3[1:(row_nr -
                                              1)], (out_3[row_nr:length(out_3)] -
                                                      gap_dur_new))
            }
            else {
              out_4 <- (out_4 + gap_dur_new)
              time_curve[, 2] <- c(out_3[1:(row_nr -
                                              1)], (out_3[row_nr:length(out_3)] +
                                                      gap_dur_new))
            }
          }
        }
        if (keep_all_time_curves == TRUE) {
          time_curve_comb <- cbind(time_curve_comb, time_curve[, 2])
        }
        anchor_depth_2 <- matrix(data = NA,
                                 ncol = 1,
                                 nrow = length(sel_rws))
        anchor_age_2 <- matrix(data = NA,
                               ncol = 1,
                               nrow = length(sel_rws))
        anchor_astro_age <- matrix(data = NA,
                                   ncol = 1,
                                   nrow = length(sel_rws))
        for (klm in 1:nrow(age_constraint_2)) {
          if (age_constraint_2[klm, 7] == "u") {
            anchor_depth_new <- runif(
              1,
              min = as.numeric(age_constraint_2[klm, 5]) - as.numeric(age_constraint_2[klm, 6]) /
                2,
              max = as.numeric(age_constraint_2[klm, 5]) + as.numeric(age_constraint_2[klm, 6]) /
                2
            )
          }
          if (age_constraint_2[klm, 7] == "n") {
            anchor_depth_new <- rnorm(
              1,
              mean = as.numeric(age_constraint_2[klm, 4]),
              sd = as.numeric(age_constraint_2[klm, 4])
            )
          }
          if (age_constraint_2[klm, 7] == "t") {
            trap_par <- as.numeric(unlist(strsplit(age_constraint_2[klm, 5], " +")))
            anchor_depth_new <- trapezoid::rtrapezoid(
              1,
              min = trap_par[1],
              mode1 = trap_par[2],
              mode2 = trap_par[3],
              max = trap_par[3],
              n1 = 2,
              n3 = 2,
              alpha = 1
            )
          }
          if (age_constraint_2[klm, 4] == "u") {
            anchor_age_new <- runif(
              1,
              min = as.numeric(age_constraint_2[klm, 2]) - as.numeric(age_constraint_2[klm, 3]) /
                2,
              max = as.numeric(age_constraint_2[klm, 2]) + as.numeric(age_constraint_2[klm, 3]) /
                2
            )
          }
          if (age_constraint_2[klm, 4] == "n") {
            anchor_age_new <- rnorm(
              1,
              mean = as.numeric(age_constraint_2[klm, 2]),
              sd = as.numeric(age_constraint_2[klm, 3])
            )
          }
          anchor_depth_2[klm] <- anchor_depth_new
          anchor_age_2[klm] <- anchor_age_new
          row_nr <- DescTools::Closest(time_curve[, 1], anchor_depth_2[klm], which = TRUE)
          anchor_astro_age[klm] <- time_curve[row_nr, 2]
        }
        ages_radio <- anchor_age_2
        ages_astro <- anchor_astro_age
        ages_astro_anchored <- ages_astro
        ages_sim <- cbind(ages_radio, ages_astro)
        if(nrow(ages_sim)>=2){
        ages_sim <- ages_sim[order(ages_sim[, 1]), ]}
        check_astro_age_2 <- check_astro_age_2[order(check_astro_age_2)]
        time_curve_anchored <- time_curve
        
        
        if (ages_sim[1, 2] > ages_sim[nrow(ages_sim), 2]) {
          a <- mean(ages_radio) + mean(ages_astro)
          ages_astro_anchored <- a - ages_astro
          time_curve_anchored[, 2] <- a - time_curve[, 2]
        }
        else {
          a <- mean(ages_radio) - mean(ages_astro)
          ages_astro_anchored <- a + ages_astro
          time_curve_anchored[, 2] <- a + time_curve[, 2]
        }
        dif_radio <- abs(check_astro_age_2 - ages_radio)
        dif_astro <- abs(check_astro_age_2 - ages_astro_anchored)
        rown_nr <- order(dif_astro, decreasing = TRUE)[1]
        dif_astro_2 <- dif_astro
        dif_astro_2[, ] <- 0
        dif_astro_2[rown_nr] <- 1
        anchor_diff <- cbind(anchor_diff, dif_astro_2)
        if ((sum(sign(dif_astro - dif_radio)) * -1) ==
            nrow(age_constraint_2)) {
          anchor_astro_age <- cbind(age_constraint[c(sel_rws), 1], ages_astro_anchored)
          anchor_astr <- -1
        }
        else {
          runs <- runs + 1
        }
        if (runs == max_runs) {
          anchor_diff_rw_sum <- matrixStats::rowSums2(as.matrix(anchor_diff))
          row_nr <- DescTools::Closest(anchor_diff_rw_sum,
                                       max(anchor_diff_rw_sum),
                                       which = TRUE)
          if (length(sel_rws) <= keep_nr) {
            anchor_diff <- anchor_diff[c(sel_rws)]
            runs <- 0
          }
          else {
            sel_rws <- sel_rws[-c(row_nr)]
            anchor_diff <- anchor_diff[c(sel_rws)]
            runs <- 0
          }
        }
      }
      if (keep_all_time_curves == TRUE) {
        result <- list(time_curve_anchored[, 2],
                       anchor_astro_age,
                       gaps_dur,
                       time_curve_comb)
      }
      else
        (result <- list(time_curve_anchored[, 2], anchor_astro_age, gaps_dur))
      res_list <- list.append(res_list, result)
      if (verbose == TRUE) {
        setTxtProgressBar(pb, i)
      }
    }
  }
  if (run_multicore == TRUE) {
    numCores <- detectCores()
    cl <- parallel::makeCluster(numCores - 2)
    registerDoSNOW(cl)
    i <- 1
    fit <- foreach(i = 1:n_simulations, .options.snow = opts) %dopar%
      {
        
        
        check_astro_age <- matrix(
          data = NA,
          ncol = 1,
          nrow = nrow(age_constraint)
        )
        anchor_depth <- matrix(
          data = NA,
          ncol = 1,
          nrow = nrow(age_constraint)
        )
        anchor_age <- matrix(
          data = NA,
          ncol = 1,
          nrow = nrow(age_constraint)
        )
        if (keep_all_time_curves == TRUE) {
          time_curve_comb <- matrix(data = NA,
                                    ncol = 0,
                                    nrow = length(x_axis))
        }
        for (klm in 1:nrow(age_constraint)) {
          if (age_constraint[klm, 4] == "u") {
            check_astro_age[klm] <- runif(
              1,
              min = tracked_cycle_period -
                tracked_cycle_period_unc,
              max = tracked_cycle_period +
                tracked_cycle_period_unc
            )
          }
          if (age_constraint[klm, 4] == "n") {
            check_astro_age[klm] <- rnorm(1,
                                          mean = as.numeric(age_constraint[klm, 2]),
                                          sd = as.numeric(age_constraint[klm, 3]))
          }
        }
        anchor_astr <- 1
        anchor_radio <- 0
        sel_rws <- seq(from = 1,
                       to = nrow(age_constraint),
                       by = 1)
        runs <- 0
        anchor_diff <- matrix(data = NA,
                              ncol = 0,
                              nrow = length(sel_rws))
        while (anchor_astr > anchor_radio) {
          age_constraint_2 <- age_constraint[c(sel_rws), ]
          check_astro_age_2 <- check_astro_age[c(sel_rws), ]
          anchor_depth_2 <- anchor_depth[c(sel_rws), ]
          anchor_age_2 <- anchor_age[c(sel_rws), ]
          validator <- 1
          while (validator == 1) {
            new_curve <- multi_tracked[, c(1, 2)]
            val <- rnorm(1, mean = multi_tracked[1, 2], sd = multi_tracked[1, 3])
            pnorm_val <- 1 - pnorm(val,
                                   mean = multi_tracked[1, 2],
                                   sd = multi_tracked[1, 3],
                                   lower.tail = FALSE)
            for (j in 1:nrow(new_curve)) {
              new_curve[j, 2] <- 1 / (qnorm(pnorm_val, mean = multi_tracked[j, 2], sd = multi_tracked[j, 3]))
            }
            if (tracked_cycle_period_unc_dist == "u") {
              tracked_cycle_period_new <- runif(
                1,
                min = tracked_cycle_period - tracked_cycle_period_unc,
                max = tracked_cycle_period + tracked_cycle_period_unc
              )
            }
            if (tracked_cycle_period_unc_dist == "n") {
              tracked_cycle_period_new <- rnorm(1, mean = tracked_cycle_period, sd = tracked_cycle_period_unc)
            }
            time_curve <- WaverideR::curve2time(
              tracked_cycle_curve = new_curve,
              tracked_cycle_period = tracked_cycle_period_new,
              genplot = FALSE,
              keep_editable = FALSE
            )
            dif_mat <- time_curve[2:(nrow(time_curve)), 2] - time_curve[1:(nrow(time_curve) -
                                                                             1), 2]
            dif_mat_min <- min(dif_mat)
            if (dif_mat_min > 0) {
              validator <- 0
            }
          }
          if (dir == FALSE) {
            time_curve[, 2] <- max(time_curve[, 2]) -
              time_curve[, 2]
          }
          gaps_dur <- 0
          if (is.null(gap_constraints) == FALSE) {
            gaps_dur <- matrix(
              data = NA,
              nrow = nrow(gap_constraints[, ]),
              ncol = 1
            )
            for (qx in 1:nrow(gap_constraints)) {
              if (gap_constraints[qx, 4] == "u") {
                if (as.numeric(gap_constraints[qx, 1]) - as.numeric(gap_constraints[qx, 2]) < 0) {
                  a <- 0
                }
                else {
                  a <- as.numeric(gap_constraints[qx, 1]) - as.numeric(gap_constraints[qx, 2])
                }
                gap_dur_new <- runif(
                  1,
                  min = a,
                  max = as.numeric(gap_constraints[qx, 1]) + as.numeric(gap_constraints[qx, 2])
                )
              }
              if (gap_constraints[qx, 4] == "n") {
                gap_dur_new <- rnorm(
                  1,
                  mean = as.numeric(gap_constraints[qx, 1]),
                  sd = as.numeric(gap_constraints[qx, 2])
                )
                if (gap_dur_new < 0) {
                  gap_dur_new <- 0
                }
              }
              gaps_dur[qx, 1] <- gap_dur_new
              row_nr <- DescTools::Closest(time_curve[, 1],
                                           as.numeric(gap_constraints[qx, 3]),
                                           which = TRUE)
              out_3 <- time_curve[, 2]
              out_4 <- out_3[row_nr:length(out_3)]
              if (dir == FALSE) {
                out_4 <- out_4 - gap_dur_new
                time_curve[, 2] <- c(out_3[1:(row_nr -
                                                1)], (out_3[row_nr:length(out_3)] -
                                                        gap_dur_new))
              }
              else {
                out_4 <- (out_4 + gap_dur_new)
                time_curve[, 2] <- c(out_3[1:(row_nr -
                                                1)], (out_3[row_nr:length(out_3)] +
                                                        gap_dur_new))
              }
            }
          }
          if (keep_all_time_curves == TRUE) {
            time_curve_comb <- cbind(time_curve_comb, time_curve[, 2])
          }
          anchor_depth_2 <- matrix(data = NA,
                                   ncol = 1,
                                   nrow = length(sel_rws))
          anchor_age_2 <- matrix(data = NA,
                                 ncol = 1,
                                 nrow = length(sel_rws))
          anchor_astro_age <- matrix(data = NA,
                                     ncol = 1,
                                     nrow = length(sel_rws))
          for (klm in 1:nrow(age_constraint_2)) {
            if (age_constraint_2[klm, 7] == "u") {
              anchor_depth_new <- runif(
                1,
                min = as.numeric(age_constraint_2[klm, 5]) - as.numeric(age_constraint_2[klm, 6]) /
                  2,
                max = as.numeric(age_constraint_2[klm, 5]) + as.numeric(age_constraint_2[klm, 6]) /
                  2
              )
            }
            if (age_constraint_2[klm, 7] == "n") {
              anchor_depth_new <- rnorm(
                1,
                mean = as.numeric(age_constraint_2[klm, 4]),
                sd = as.numeric(age_constraint_2[klm, 4])
              )
            }
            if (age_constraint_2[klm, 7] == "t") {
              trap_par <- as.numeric(unlist(strsplit(age_constraint_2[klm, 5], " +")))
              anchor_depth_new <- trapezoid::rtrapezoid(
                1,
                min = trap_par[1],
                mode1 = trap_par[2],
                mode2 = trap_par[3],
                max = trap_par[3],
                n1 = 2,
                n3 = 2,
                alpha = 1
              )
            }
            if (age_constraint_2[klm, 4] == "u") {
              anchor_age_new <- runif(
                1,
                min = as.numeric(age_constraint_2[klm, 2]) - as.numeric(age_constraint_2[klm, 3]) /
                  2,
                max = as.numeric(age_constraint_2[klm, 2]) + as.numeric(age_constraint_2[klm, 3]) /
                  2
              )
            }
            if (age_constraint_2[klm, 4] == "n") {
              anchor_age_new <- rnorm(
                1,
                mean = as.numeric(age_constraint_2[klm, 2]),
                sd = as.numeric(age_constraint_2[klm, 3])
              )
            }
            anchor_depth_2[klm] <- anchor_depth_new
            anchor_age_2[klm] <- anchor_age_new
            row_nr <- DescTools::Closest(time_curve[, 1], anchor_depth_2[klm], which = TRUE)
            anchor_astro_age[klm] <- time_curve[row_nr, 2]
          }
          ages_radio <- anchor_age_2
          ages_astro <- anchor_astro_age
          ages_astro_anchored <- ages_astro
          ages_sim <- cbind(ages_radio, ages_astro)
        if(nrow(ages_sim)>=2){
        ages_sim <- ages_sim[order(ages_sim[, 1]), ]}
          check_astro_age_2 <- check_astro_age_2[order(check_astro_age_2)]
          time_curve_anchored <- time_curve

          if (ages_sim[1, 2] > ages_sim[nrow(ages_sim), 2]) {
            a <- mean(ages_radio) + mean(ages_astro)
            ages_astro_anchored <- a - ages_astro
            time_curve_anchored[, 2] <- a - time_curve[, 2]
          }
          else {
            a <- mean(ages_radio) - mean(ages_astro)
            ages_astro_anchored <- a + ages_astro
            time_curve_anchored[, 2] <- a + time_curve[, 2]
          }
          dif_radio <- abs(check_astro_age_2 - ages_radio)
          dif_astro <- abs(check_astro_age_2 - ages_astro_anchored)
          rown_nr <- order(dif_astro, decreasing = TRUE)[1]
          dif_astro_2 <- dif_astro
          dif_astro_2[, ] <- 0
          dif_astro_2[rown_nr] <- 1
          anchor_diff <- cbind(anchor_diff, dif_astro_2)
          sel_proxy_nr <- round(runif(
            n = 1,
            min = 1,
            max = length(proxy_data)
          ), 0)
          my.data <- proxy_data[[sel_proxy_nr]]
          out <- time_curve_anchored
          completed_series <- na.omit(out)
          yleft_comp <- completed_series[1, 2]
          yright_com <- completed_series[nrow(completed_series), 2]
          app <- approx(
            x = out[, 1],
            y = out[, 2],
            xout = my.data[, 1],
            method = "linear",
            yleft = yleft_comp,
            yright = yright_com
          )
          completed_series <- as.data.frame(cbind(app$y, my.data[, 2]))
          dat <- as.matrix(completed_series)
          dat <- na.omit(dat)
          dat <- dat[order(dat[, 1], na.last = NA, decreasing = F), ]
          npts <- length(dat[, 1])
          start <- dat[1, 1]
          end <- dat[length(dat[, 1]), 1]
          x1 <- dat[1:(npts - 1), 1]
          x2 <- dat[2:(npts), 1]
          dx = x2 - x1
          dt = median(dx)
          xout <- seq(start, end, by = dt)
          npts <- length(xout)
          interp <- approx(dat[, 1], dat[, 2], xout, method = "linear", n = npts)
          completed_series <- as.data.frame(interp)
          wt_res <- WaverideR::analyze_wavelet(
            data = completed_series,
            dj = dj,
            lowerPeriod = lowerPeriod,
            upperPeriod = upperPeriod,
            verbose = FALSE,
            omega_nr = omega_nr
          )
          avg_wt <- cbind(wt_res$Period, wt_res$Power.avg)
          avg_wt <- WaverideR::max_detect(data = avg_wt, pts = 5)
          mtm_res_test <- is.na(avg_wt)
          mtm_res_test <- mtm_res_test[1]
          if (mtm_res_test == TRUE) {
            runs <- runs + 1
          }
          else if ((sum(sign(dif_astro - dif_radio)) *
                    -1) == nrow(age_constraint_2) |
                   nrow(age_constraint_2) ==
                   1) {
            mtm_per <- avg_wt[, 1]
            high_vals <- cycles_check + uncer_cycles_check
            low_vals <- cycles_check - uncer_cycles_check
            check <- matrix(
              data = NA,
              nrow = length(cycles_check),
              ncol = 1
            )
            for (i in 1:length(cycles_check)) {
              check[i, 1] <- any(mtm_per < high_vals[i] &
                                   mtm_per > low_vals[i])
            }
            if (sum(check) == length(cycles_check)) {
              anchor_astro_age <- cbind(age_constraint[c(sel_rws), 1], ages_astro_anchored)
              anchor_astr <- -1
            }
            else {
              runs <- runs + 1
              print(runs)
            }
          }
          else {
            runs <- runs + 1
            print(runs)
          }
          if (runs == max_runs) {
            anchor_diff_rw_sum <- matrixStats::rowSums2(as.matrix(anchor_diff))
            row_nr <- DescTools::Closest(anchor_diff_rw_sum,
                                         max(anchor_diff_rw_sum),
                                         which = TRUE)
            if (length(sel_rws) <= keep_nr) {
              anchor_diff <- anchor_diff[c(sel_rws)]
              runs <- 0
            }
            else {
              sel_rws <- sel_rws[-c(row_nr)]
              anchor_diff <- anchor_diff[c(sel_rws)]
              runs <- 0
            }
          }
        }
        time_curve_anchored <- time_curve_anchored[, 2]
        if (keep_all_time_curves == TRUE) {
          result <- list(time_curve_anchored,
                         anchor_astro_age,
                         gaps_dur,
                         time_curve_comb)
        }
        else {
          (result <- list(time_curve_anchored, anchor_astro_age, gaps_dur))
        }
      }
    res_list <- fit
    stopCluster(cl)
  }
}
result_matrix <- matrix(data = NA,
                        nrow = nrow(age_curve),
                        ncol = 0)
res_radio <- matrix(data = NA, nrow = 0, ncol = 2)
colnames(res_radio) <- c("name", "age")
res_time_runs <- matrix(data = NA,
                        nrow = length(x_axis),
                        ncol = 0)
if (is.null(gap_constraints) == FALSE & keep_all_time_curves ==
    FALSE) {
  res_gap <- matrix(data = NA,
                    nrow = 0,
                    ncol = nrow(gap_constraints))
  colnames(res_gap) <- c(gap_constraints[, 3])
  for (i in 1:length(res_list)) {
    time_curve <- res_list[[i]][[1]]
    new_anchor_dates <- res_list[[i]][[2]]
    new_gap_dur <- res_list[[i]][[3]]
    result_matrix <- cbind(result_matrix, time_curve)
    colnames(new_anchor_dates) <- c("name", "age")
    res_radio <- rbind(res_radio, new_anchor_dates)
    new_gap_dur <- t(new_gap_dur)
    colnames(new_gap_dur) <- c(gap_constraints[, 3])
    res_gap <- rbind(as.matrix(res_gap), new_gap_dur)
    res_radio_split <- split(x = res_radio[, 2], f = as.factor(unique(res_radio[, 1])))
    for (i in 1:length(res_radio_split)) {
      res_radio_split[[i]] <- as.numeric(res_radio_split[[i]])
    }
  }
  result <- list(x_axis, result_matrix, res_gap, res_radio_split)
}
if (is.null(gap_constraints) == TRUE & keep_all_time_curves ==
    FALSE) {
  for (i in 1:length(res_list)) {
    time_curve <- res_list[[i]][[1]]
    new_anchor_dates <- res_list[[i]][[2]]
    result_matrix <- cbind(result_matrix, time_curve)
    colnames(new_anchor_dates) <- c("name", "age")
    res_radio <- rbind(res_radio, new_anchor_dates)
    res_radio_split <- split(x = res_radio[, 2], f = as.factor(unique(res_radio[, 1])))
    for (i in 1:length(res_radio_split)) {
      res_radio_split[[i]] <- as.numeric(res_radio_split[[i]])
    }
  }
  result <- list(x_axis, result_matrix, res_radio_split)
}
if (is.null(gap_constraints) == FALSE & keep_all_time_curves ==
    TRUE) {
  res_gap <- matrix(data = NA,
                    nrow = 0,
                    ncol = nrow(gap_constraints))
  colnames(res_gap) <- c(gap_constraints[, 3])
  for (i in 1:length(res_list)) {
    time_curve <- res_list[[i]][[1]]
    new_anchor_dates <- res_list[[i]][[2]]
    new_gap_dur <- res_list[[i]][[3]]
    time_curve_all <- res_list[[i]][[4]]
    result_matrix <- cbind(result_matrix, time_curve)
    colnames(new_anchor_dates) <- c("name", "age")
    res_radio <- rbind(res_radio, new_anchor_dates)
    new_gap_dur <- t(new_gap_dur)
    colnames(new_gap_dur) <- c(gap_constraints[, 3])
    res_gap <- rbind(as.matrix(res_gap), new_gap_dur)
    res_time_runs <- cbind(res_time_runs, time_curve_all)
    res_radio_split <- split(x = res_radio[, 2], f = as.factor(unique(res_radio[, 1])))
    for (i in 1:length(res_radio_split)) {
      res_radio_split[[i]] <- as.numeric(res_radio_split[[i]])
    }
  }
  result <- list(x_axis,
                 result_matrix,
                 res_gap,
                 res_radio_split,
                 res_time_runs)
}
if (is.null(gap_constraints) == TRUE & keep_all_time_curves ==
    TRUE) {
  for (i in 1:length(res_list)) {
    time_curve <- res_list[[i]][[1]]
    new_anchor_dates <- res_list[[i]][[2]]
    time_curve_all <- res_list[[i]][[4]]
    result_matrix <- cbind(result_matrix, time_curve)
    colnames(new_anchor_dates) <- c("name", "age")
    res_radio <- rbind(res_radio, new_anchor_dates)
    res_time_runs <- cbind(res_time_runs, time_curve_all)
    res_radio_split <- split(x = res_radio[, 2], f = as.factor(unique(res_radio[, 1])))
    for (i in 1:length(res_radio_split)) {
      res_radio_split[[i]] <- as.numeric(res_radio_split[[i]])
    }
  }
  result <- list(x_axis, result_matrix, res_radio_split, res_time_runs)
}
if (genplot == TRUE) {
  plot(
    age_constraint[, 5],
    age_constraint[, 2],
    col = "black",
    cex = 2,
    type = "p",
    ylim = c(min(result[[2]]), max(result[[2]])),
    xlim = c(max(x_axis), min(x_axis))
  )
  points(
    age_constraint[, 5],
    as.numeric(age_constraint[, 2]) + 2 * as.numeric(age_constraint[, 3]),
    col = "red",
    cex = 1,
    pch = 6
  )
  points(
    age_constraint[, 5],
    as.numeric(age_constraint[, 2]) - 2 * as.numeric(age_constraint[, 3]),
    col = "blue",
    cex = 1,
    pch = 2
  )
  res <- result[[2]]
  sds <- rowSds(result[[2]])
  lines(x_axis, rowMeans(result[[2]]), col = "black")
  lines(x_axis, rowMeans(result[[2]]) + 2 * sds, col = "red")
  lines(x_axis, rowMeans(result[[2]]) - 2 * sds, col = "blue")
}
  return(result)
}

induration <- read.csv(
  "https://raw.githubusercontent.com/stratigraphy/Kosov-cyclostrat/main/induration.csv"
)

induration <- sortNave(induration[,c(2,1)],genplot=FALSE)
induration <- linterp(induration,0.01,genplot=FALSE)

induration[,1] <- seq(25,-7.20,length.out=length(induration[,1]))
induration <- linterp(induration,0.01,genplot=FALSE)
induration <- mwStats(induration,win=0.05,ends=TRUE,genplot=FALSE)
induration <- linterp(induration,0.01,genplot=FALSE)

induration[,2] <- induration[,2]/max(induration[,2])
induration[,2] <- sqrt(induration[,2])
induration[,2] <- induration[,2]-min(induration[,2])
induration[,2] <- induration[,2]/max(induration[,2])
induration[,2] <- induration[,2]*5


kosov_isotopes <- read.csv(
  "https://raw.githubusercontent.com/stratigraphy/Kosov-cyclostrat/main/kosov_isotopes.csv",header =TRUE
)

kosov_d13Ccarb <- kosov_isotopes[,c(3,6)]
kosov_d13Ccarb <- na.omit(kosov_d13Ccarb)
kosov_d13Ccarb <- sortNave(kosov_d13Ccarb,genplot=FALSE)

kosov_d18O <- kosov_isotopes[,c(3,4)]
kosov_d18O <- na.omit(kosov_d18O)
kosov_d18O <- sortNave(kosov_d18O,genplot=FALSE)

kosov_d13Corg<- kosov_isotopes[,c(3,5)]
kosov_d13Corg <- na.omit(kosov_d13Corg)
kosov_d13Corg <- sortNave(kosov_d13Corg,genplot=FALSE)


kosov_big13C <- kosov_isotopes[,c(3,5,6)]
kosov_big13C <- na.omit(kosov_big13C)
kosov_big13C[,2] <- kosov_big13C[,3]-kosov_big13C[,2]

# URL of the raw image
url <- "https://raw.githubusercontent.com/stratigraphy/Kosov-cyclostrat/main/kosov_log.jpg"

# Download into memory
res <- GET(url)
stop_for_status(res)

# Read the JPEG from the raw content
img <- readJPEG(content(res, "raw"))

# URL of the raw image
url_2 <- "https://raw.githubusercontent.com/stratigraphy/Kosov-cyclostrat/main/regional_overview.jpg"

# Download into memory
url_2_get <- GET(url_2)
stop_for_status(url_2_get)

# Read the JPEG from the raw content
img_2 <- readJPEG(content(url_2_get, "raw"))

my_data <- data.frame(
  Interval = c("Entire record",
               "Lau Biogeochemical Event", 
               "LKB Event (trace metal peak)",
               "R-zone", 
               "S-Zone", 
               "F-Zone",
               "Ludlow part section",
               "Pridoli part section"),
  position_bottom =c(-7.20,
               -0.4, 
               -0.4,
               0, 
               1.4, 
               11.6,
               0,
               22),
  position_top=c(25,
               20, 
               0,
               1.4, 
               11.6, 
               20,
               22,
               25))
my_data$age_bottom <- NA
my_data$uncertainty_age_bottom <- NA
my_data$age_top <- NA
my_data$uncertainty_age_top <- NA
my_data$duration <- NA
my_data$uncertainty <- NA

Abstract

The Kosov Quarry section in the Prague Basin preserves one of the most complete Silurian successions spanning the Lau Biogeochemical Event. This interval records major perturbations in the carbon cycle, climate cooling, redox fluctuations, and faunal turnover, yet the pacing and duration of these changes remain poorly constrained. Leveraging its exceptional completeness, we developed a new astrochronology based on the induration record, which allows the identification of cycles ranging from the half-precession to the 405-kyr eccentricity cycle. The anchored astrochronology that indicates that the Lau Biogeochemical Event started at 424.06 ± 0.55 Ma and lasted 0.87 ± 0.09 Myr, while the associated Lau-Kozlowski Bio-event was much shorter at 30 ± 10 kyr. Astronomical forcing is expressed not only in the induration record but also in the rate of change (‰/kyr) of the δ13Ccarb curve and in the lag-1 sea-level proxy. The δ13Ccarb rate-of-change record shows a strong 100-kyr eccentricity imprint during the onset of the Lau Event, reaching a maximum of 0.09 ‰/kyr. Similarly, the lag-1 sea-level record is dominated by the 405-kyr eccentricity cycle and appears in-phase with insolation, consistent with (405-kyr) eccentricity-paced glacio-eustatic sea-level fluctuations during the Ludlow.

1. Introduction

The Lau Biogeochemical Event is associated with one of the largest carbon cycle perturbations of the entire Phanerozoic. The Lau Biogeochemical Event is marked by a large positive δ¹³C excursion, faunal turnover, and evidence for sea-level and redox fluctuations (Frýda and Manda, 2013; Frýda et al., 2020). These changes indicate a tight coupling between the carbon cycle, climate, and oceanographic processes; however, the tempo and (astronomical) pacing of the Event remain poorly constrained. Without a precise temporal framework, it is not possible to determine whether the Lau reflects a brief perturbation or a protracted interval of instability.

To address this, we will conduct a cyclostratigraphic study on the Kosov quarry section, which provides a nearly complete archive, allowing us to establish a high-resolution astrochronological framework for the Lau Biogeochemical Event. The induration record of Frýda et al. (2020) will form the basis for constructing the astrochronology, which will enable us to assign durations and associated uncertainties to the Lau Biogeochemical Event and its associated (sub)intervals. The lag-1 sea-level proxy will be derived from the tuned induration record, which will provide constraints and insights into sea-level variability during the Lau Biogeochemical event. The rate of change (‰/kyr) calculated from the δ13Ccarb record will be used to gain insights into carbon cycle dynamics during the Lau Biogeochemical Event. Furthermore, the imprint of astronomical cycles on the δ13Ccarb rate of change (‰/kyr) and lag-1 sea-level will be delineated to understand the role that astronomical cycles might have played in pacing climate, organic carbon burial, and sea-level variations during the Lau Biogeochemical Event.

1.1 Geological setting

Our study focuses on upper Silurian (Ludfordian) strata of the Prague Basin, a sedimentary succession within the Barrandian (Teplá-Barrandian terrane) (Frýda and Manda, 2013; Frýda et al., 2020, 2021a) (Figure 1). During the Ludfordian, this block was located at ~25–30°S (Tasáryová et al., 2014; Scotese, 2021). Facies distribution indicates hemipelagic deposition surrounded by shallow- to deeper-marine environments (HORNÝ, 1955; Kříž, 1991). It remains debated whether the Barrandian’s geodynamic setting belongs to the peri-Gondwanan terrains or is its own isolated Perunica microcontinent (Fatka and Mergl, 2009).

Code
par(mar = c(4, 2, 2, 2))

# Empty plot to define margins and space in the panel
plot(1, type = "n", axes = FALSE, xlab = "", ylab = "", main="A")

# Get the coordinates of the current plot region
usr <- par("usr")  # c(x1, x2, y1, y2)

# Draw the image to fill the entire panel
rasterImage(img_2, usr[1], usr[3], usr[2], usr[4])

Figure 1. Regional overview of the Kosov section. Copied from Fryda et al. (2020)
Code
par(mar = c(4, 6, 2, 2))

The studied Kosov section (No.JF195; 49°56′12.2″N, 14°03′20.1″E) provides one of the most complete global records of the mid-Ludfordian carbon isotope excursion (Frýda and Manda, 2013; Frýda et al., 2020, 2021a) (Figure 2).The succession comprises carbonates with marly interbeds and shales, including micritic mudstones, skeletal limestones, and crinoidal grainstones (Kříž, 1992; Lehnert et al., 2007). The site has been widely studied for its sedimentology, palaeontology, and faunal changes across the Lau/Kozlowskii Bioevent (LKB) and Mid-Ludfordian Carbon Isotope Excursion (MLCIE) (Kříž, 1992; Štorch, 1995; Manda and Kříž, 2006; Frýda and Manda, 2013).

Code
# Those data are embedded into WaverideR
#plot the data

Ludlow_col <- geo_col(name = "Ludlow")
Pridoli_col <- geo_col(name = "Pridoli")

layout.matrix <- matrix(c(1, 2,3,4,5), nrow = 1, ncol = 5)
graphics::layout(mat = layout.matrix,
                 heights = c(1,1,1,1), # Heights of the two rows
                 widths = c(1.5,0.75,0.5,1.5,1,1)) # Widths of the two columns

par(mar = c(4, 4, 2, 2))

# Empty plot to define margins and space in the panel
plot(1, type = "n", axes = FALSE, xlab = "", ylab = "", main="A")

# Get the coordinates of the current plot region
usr <- par("usr")  # c(x1, x2, y1, y2)

# Draw the image to fill the entire panel
rasterImage(img, usr[1], usr[3], usr[2], usr[4])
par(new=TRUE)

a <- round(c(max(induration[, 1]), min(induration[, 1])),0)
ylims <- c(min(induration[, 1]),max(induration[,1]))

plot(
  x = c(0, 1),
  y = c(min(induration[, 1]), max(induration[, 1])),
  col = "white",
  xlab = "",
  ylab = "Position (m)",
  xaxt = "n",
  xaxs = "i",
  yaxs = "i",
  yaxt = "n",
  ylim = ylims,axes=FALSE,
  main=""
)    


axis(2, at = (seq(a[1],
                          a[2], by = -1)),
     labels = (seq(a[1],
                      a[2], by = -1)))

par(mar = c(4, 4, 2, 0))

plot(
  x = c(0, 1),
  y = c(min(induration[, 1]), max(induration[, 1])),
  col = "white",
  xlab = "",
  ylab = "Postion (m)",
  xaxt = "n",
  xaxs = "i",
  yaxs = "i",
  yaxt= "n",
  ylim = ylims,
  main="B"
)    

axis(2, at = (seq(a[1],
                          a[2], by = -1)),
     labels = (seq(a[1],
                      a[2], by = -1)))

polygon(
    y = c(
      my_data[1,2],
      my_data[1,2],
      my_data[8,2],
      my_data[8,2]
    ),
    x = c(0, 1, 1, 0),
    col = Ludlow_col
  )

    polygon(
    y = c(
      my_data[8,2],
      my_data[8,2],
      my_data[8,3],
      my_data[8,3]
    ),
    x = c(0, 1, 1, 0),
    col = Pridoli_col
  )

    text(
    labels = "Ludlow",
    x = 0.5,
    srt = 270,cex=2,
    y = (my_data[1,2] +my_data[8,2]) / 2
  )

    text(
    labels = "Pridoli",
    x = 0.5,
    srt = 270,cex=2,
    y = (my_data[8,2] + my_data[8,3]) / 2
  )
 
   
par(mar = c(4, 0.5, 2, 0.5))


plot(
  x = c(0, 1),
  y = c(min(induration[, 1]), max(induration[, 1])),
  col = "white",
  xlab = "",
  ylab = "",
  xaxt = "n",
  xaxs = "i",
  yaxs = "i",
  yaxt = "n",
  ylim = ylims,
  main="C"
)    

polygon(
    y = c(
      my_data[4,2],
      my_data[4,2],
      my_data[5,2],
      my_data[5,2]
    ),
    x = c(0, 1, 1, 0),
    col = "lightgrey"
  )
    text(
      labels = "R-zone",cex=1,
    x = 0.5,
    srt = 270,
    y = (my_data[4,2] + my_data[5,2]) / 2
  )

polygon(
    y = c(
      my_data[5,2],
      my_data[5,2],
      my_data[6,2],
      my_data[6,2]
    ),
    x = c(0, 1, 1, 0),
    col = "lightgrey"
  )
    text(
    labels = "S-zone",cex=2,
    x = 0.5,
    srt = 270,
    y = (my_data[6,2] + my_data[5,2]) / 2
  )

polygon(
    y = c(
      my_data[6,2],
      my_data[6,2],
      my_data[6,3],
      my_data[6,3]
    ),
    x = c(0, 1, 1, 0),
    col = "lightgrey"
  )
    text(
    labels = "F-zone",cex=2,
    x = 0.5,
    srt = 270,
    y = (my_data[6,2] + my_data[6,3]) / 2
  )

plot(induration[,2],induration[,1],type="l",yaxs="i",frame.plot=FALSE,ylab="Position (m)",xlab="induration",ylim=range(induration[,1]), main="D",yaxt="n")
par(mar = c(4, 1, 2, 2))

plot(kosov_d13Ccarb[,2],kosov_d13Ccarb[,1],type="l",yaxs="i",frame.plot=FALSE,ylab="Position (m)",xlab="δ13Ccarb",yaxt="n", main="E",col="green",ylim=range(induration[,1]))
points(kosov_d13Ccarb[,2],kosov_d13Ccarb[,1],col="darkgreen",pch=19)

polygon(
  x =
    c(
      min(kosov_d13Ccarb[, 2]) - 0.15,
      max(kosov_d13Ccarb[, 2]) * 1.1,
      max(kosov_d13Ccarb[, 2]) * 1.1,
      min(kosov_d13Ccarb[, 2]) - 0.15
    ),
  y = c(
    my_data[6,3],
    my_data[6,3],
    my_data[4,2],
    my_data[4,2]
  ),
  col = rgb(0, 0, 0, 0.25)
)

 text(
    labels = "Lau  δ13Ccarb excursion",
    x = 2,
    srt = 270,
    y = (my_data[4,2] + my_data[6,3]) / 2
  )
 
 
abline(h=my_data[3,2],col="red",lty=3,lwd=3) 

    text(
    labels = "Onset \n LKB",
    x =6,cex=1.25,
    srt = 0,
    y = (my_data[3,2])
  ) 

Figure 2. The original litholog, chronostratigraphy, event zonation, induration record and the δ13Ccarb curve. A. The Litholog of Frýda et al. (2020). B. Chronostratigraphic subdivision. C. Event stratigraphic intervals. D. The induration record extracted from the litholog E. The δ13Ccarb record of Frýda et al. (2020)

2. Materials and Methods

A cyclostratigraphic analysis was conducted on the induration record extracted from the litholog published in Frýda et al. (2020) (see https://github.com/stratigraphy/Kosov-cyclostrat for the R code). The induration record is considered as a proxy for lithification intensity and sedimentary competence, with low values corresponding to soft shales and marl and high values corresponding to competent limestone beds. The analyses leveraged the functions of the WaverideR (Arts, 2023; Arts et al., 2024) and astrochron R packages (Meyers, 2015, 2019) to perform the cyclostratigraphic study.

The cyclostratigraphic analysis began by applying the Time Optimisation (TimeOpt) function of the astrochron R package to the induration record, yielding a first-order estimate of the sedimentation rate (Meyers, 2015). The TimeOpt function was run twice: once to evaluate the 405-kyr eccentricity amplitude modulation of the 100-kyr eccentricity band and the concentration of power at the 100-kyr and 405-kyr eccentricity frequencies, and once to evaluate the eccentricity amplitude modulation of the precession band and the concentration of power at the precession and eccentricity frequencies. The optimal fit of the astronomical cycles was evaluated for a range of sediment accumulation rates spanning between 0.1 and 4 cm/kyr, which covers the minimum and maximum estimates for the durations of the studied interval (Cramer et al., 2015; Melchin et al., 2020; Arts et al., 2024).

The first-order sedimentation rate estimate from the TimeOpt runs was used to extract the 405-kyr eccentricity cycle from the induration record. The extracted cycle was then used to conduct a minimal tuning, where the distance between two successive peaks of the extracted cycle was set to the duration of the interpreted astronomical cycle. This duration was subsequently converted into an age model and a sedimentation-rate curve. The sedimentation-rate curve was then used to convert cm/kyr to the period (m) of different astronomical cycles. These cycles were then plotted on top of the CWT scalogram to verify whether the curves passed through areas of high spectral power, validating the presence of astronomical cycles. When this was the case, the period (m) of the 405-kyr eccentricity cycle was tracked in the CWT.

The tracked period (m) curve of the 405-kyr eccentricity cycle was then used to create a floating astrochronology with an assigned uncertainty based on the analytical uncertainty of the Wavelet (Arts et al., 2024). This floating age model with uncertainty was then anchored to the astrochronologically calibrated age of the Ludlow–Pridoli boundary (423.03 ±0.53 Ma) (Arts et al., 2024). The anchored numerical age model was then used to assign ages and durations (with uncertainty) to the record of the Kosov quarry section and its subsections.

The mean age model was then used to convert the induration, allowing for the study of the imprint of astronomical cycles in the induration record in the time domain. Special attention was paid to the phase relationship between the 100-kyr eccentricity cycle directly extracted from the record and the 100-kyr eccentricity cycle extracted from the Hilbert transform of the precession cycle, and between the 405-kyr eccentricity cycle directly extracted from the record and the 405-kyr eccentricity cycle extracted from the Hilbert transform of the 100-kyr eccentricity cycle. The phase relationship between these cycles allowed the inference of the phase relationship between cycles extracted from the proxy record and the true phase of the astronomical cycle, as well as its relationship with changes in insolation (Hinnov, 2000; Laskar et al., 2004).

The δ13Ccarb record was also converted to the time domain which allowed the for the calculation of the rate of change (‰/kyr) in the tuned δ13Ccarb record. This record was then further investigated for the imprint of astronomical cycles.

A lag-1 curve was also generated based by applying a windowed lag-1 autocorrelation coefficient Monte Carlo simulation on the induration record in the time domain. This record functions as a proxy record for the sea-level curve (Li et al., 2018). The imprint of astronomical cycles in the lag-1 record was investigated and compared to the imprint of astronomical cycles in the induration record to understand the relationship between astronomical forcing and sea-level change during the Lau Biogeochemical Event.

3. Results

3.1.TimeOpt-derived sedimentation rates

The TimeOpt function was run twice, once to optimize the 405-kyr eccentricity amplitude modulation of the 100-kyr eccentricity band, and the concentration of power at the 100-kyr and 405-kyr eccentricity frequencies and once to optimize the eccentricity amplitude modulation of the precession band, and the concentration of power at the precession and eccentricity frequencies. The TimeOpt results indicate that the optimal sedimentation rate is around 3 cm/ka, with the peak spanning sedimentation rates between 2 and 4 cm/ka (see Figure 3).

Code
#run timeOpt
#we need to run it twice for a correct plot later on
targetP_AM=c(405.7, 130.7, 123.8, 98.9,94.9)
targetP=c(21,17)

res1=timeOpt(dat = induration,
    sedmin = 0.1,
    sedmax = 4,
    numsed = 500,
    fit = 2,
    fitModPwr = TRUE,
    targetE=targetP_AM,
    roll = 10^4,output=1)

res2 <- timeOpt(dat = induration,
    sedmin = 0.1,
    sedmax = 4,
    numsed = 500,
    fit = 2,
    fitModPwr = TRUE,
    targetE=targetP_AM,
    roll = 10^4,output=2,genplot=TRUE)


res3=timeOpt(dat = induration,
    sedmin = 0.1,
    sedmax = 4,
    numsed = 500,
    fit = 1,
    fitModPwr = TRUE,
    targetP=targetP,
    roll = 10^4,output=1)

res4 <- timeOpt(dat = induration,
    sedmin = 0.1,
    sedmax = 4,
    numsed = 500,
    fit = 1,
    fitModPwr = TRUE,
    targetP=targetP,
    roll = 10^4,output=2,genplot=TRUE)
Code
layout(matrix(c(1, 2, 3, 4,5,6,7,8), nrow = 4, ncol = 2, byrow = TRUE))  
par(mar = c(4, 4, 3, 3))

# --- Panel A ---
plot(res1[, 1], res1[, 2], cex = 0.75, cex.lab = 1.2, 
     cex.main = 1.3, col = "red", xlab = "", ylab = "", 
     main = expression(paste(bold("Fit: "), {
       "r"^2
     }["envelope"], " (red) and ", {
       "r"^2
     }["power"], " (gray)")))
axis(2, col.axis = "red")
mtext(expression("r"^2), side = 2, line = 2, cex = 0.9)
mtext("Sedimentation rate (cm/ka)", side = 1, line = 2.3, cex = 0.8)
par(new = TRUE)
plot(res1[, 1], res1[, 3], col = "#00000064", 
     xlab = "", ylab = "", type = "l", axes = FALSE, lwd = 2)
axis(4, ylim = c(0, max(res1[, 3])), lwd = 1, col = "black")
usr <- par("usr")
text(usr[1], usr[4] - 0.05*(usr[4]-usr[3]), "A", pos = 4, font = 2, cex = 1.5)

# --- Panel B ---
plot(res1[, 1], res1[, 4], type = "l", lwd = 2, 
     cex.lab = 1.2, cex.main = 1.3, col = "black", 
     xlab = "", ylab = "", main = expression(paste(bold("Optimal Fit: "), {
       "r"^2
     }["opt"])))
mtext(expression({
  "r"^2
}["opt"]), side = 2, line = 1.9, cex = 0.9)
mtext("Sedimentation rate (cm/ka)", side = 1, line = 2.3, cex = 0.8)
usr <- par("usr")
text(usr[1], usr[4] - 0.05*(usr[4]-usr[3]), "B", pos = 4, font = 2, cex = 1.5)

# --- Panel C ---
ylim1 = c(min(res2[,4], res2[,5]), max(res2[,4], res2[,5]))
plot(res2[,1], res2[,4], cex = 0.5, cex.lab = 1.2, cex.main = 1.2, 
     xlab = "", ylab = "", main = "Envelope (red); Reconstructed Ecc. Model (black)", 
     col = "red", type = "l", ylim = ylim1)
lines(res2[, 1], res2[,5], lwd = 1.5)
mtext("Std. Value", side = 2, line = 2, cex = 0.9)
mtext("Time (ka)", side = 1, line = 2.3, cex = 0.8)
usr <- par("usr")
text(usr[1], usr[4] - 0.05*(usr[4]-usr[3]), "C", pos = 4, font = 2, cex = 1.5)

# --- Panel D ---
ylim1 = c(min(res2[, 3], -1 * res2[, 4]), max(res2[, 3], res2[, 4]))
plot(res2[,1],res2[,3], col = "blue", cex = 0.5, cex.lab = 1.2, 
     cex.main = 1.3, xlab = "", ylab = "", 
     main = "Envelope (red); Filtered Data (blue)", ylim = ylim1)
lines(res2[,1],res2[,3], col = "blue")
lines(res2[,1],res2[,4], col = "red")
lines(res2[,1], -1 *res2[,4], col = "red")
abline(h = 0, col = "black", lty = 3)
mtext("Std. Value", side = 2, line = 2, cex = 0.9)
mtext("Time (ka)", side = 1, line = 2.3, cex = 0.8)
usr <- par("usr")
text(usr[1], usr[4] - 0.05*(usr[4]-usr[3]), "D", pos = 4, font = 2, cex = 1.5)



# --- Panel E ---
plot( res3[, 1],  res3[, 2], cex = 0.75, cex.lab = 1.2, 
     cex.main = 1.3, col = "red", xlab = "", ylab = "", 
     main = expression(paste(bold("Fit: "), {
       "r"^2
     }["envelope"], " (red) and ", {
       "r"^2
     }["power"], " (gray)")))
axis(2, col.axis = "red")
mtext(expression("r"^2), side = 2, line = 2, cex = 0.9)
mtext("Sedimentation rate (cm/ka)", side = 1, line = 2.3, cex = 0.8)
par(new = TRUE)
plot( res3[, 1],  res3[, 3], col = "#00000064", 
     xlab = "", ylab = "", type = "l", axes = FALSE, lwd = 2)
axis(4, ylim = c(0, max( res3[, 3])), lwd = 1, col = "black")
usr <- par("usr")
text(usr[1], usr[4] - 0.05*(usr[4]-usr[3]), "E", pos = 4, font = 2, cex = 1.5)

# --- Panel F ---
plot( res3[, 1],  res3[, 4], type = "l", lwd = 2, 
     cex.lab = 1.2, cex.main = 1.3, col = "black", 
     xlab = "", ylab = "", main = expression(paste(bold("Optimal Fit: "), {
       "r"^2
     }["opt"])))
mtext(expression({
  "r"^2
}["opt"]), side = 2, line = 1.9, cex = 0.9)
mtext("Sedimentation rate (cm/ka)", side = 1, line = 2.3, cex = 0.8)
usr <- par("usr")
text(usr[1], usr[4] - 0.05*(usr[4]-usr[3]), "F", pos = 4, font = 2, cex = 1.5)

# --- Panel G ---
ylim1 = c(min( res4[,4],  res4[,5]), max( res4[,4],  res4[,5]))
plot( res4[,1],  res4[,4], cex = 0.5, cex.lab = 1.2, cex.main = 1.2, 
     xlab = "", ylab = "", main = "Envelope (red); Reconstructed Ecc. Model (black)", 
     col = "red", type = "l", ylim = ylim1)
lines( res4[, 1],  res4[,5], lwd = 1.5)
mtext("Std. Value", side = 2, line = 2, cex = 0.9)
mtext("Time (ka)", side = 1, line = 2.3, cex = 0.8)
usr <- par("usr")
text(usr[1], usr[4] - 0.05*(usr[4]-usr[3]), "G", pos = 4, font = 2, cex = 1.5)

# --- Panel H ---
ylim1 = c(min( res4[, 3], -1 *  res4[, 4]), max( res4[, 3],  res4[, 4]))
plot( res4[,1], res4[,3], col = "blue", cex = 0.5, cex.lab = 1.2, 
     cex.main = 1.3, xlab = "", ylab = "", 
     main = "Envelope (red); Filtered Data (blue)", ylim = ylim1)
lines( res4[,1], res4[,3], col = "blue")
lines( res4[,1], res4[,4], col = "red")
lines( res4[,1], -1 * res4[,4], col = "red")
abline(h = 0, col = "black", lty = 3)
mtext("Std. Value", side = 2, line = 2, cex = 0.9)
mtext("Time (ka)", side = 1, line = 2.3, cex = 0.8)
usr <- par("usr")
text(usr[1], usr[4] - 0.05*(usr[4]-usr[3]), "H", pos = 4, font = 2, cex = 1.5)

Figure 3. TimeOpt results. A. Fit: r²[envelope] (red) and r²[power] (grey) for TimeOpt run 405 vs 100-kyr ecc. B. Optimal Fit: r²[opt] for TimeOpt run 405 vs 100-kyr ecc. C. Envelope (red); Reconstructed Ecc. Model (black) for TimeOpt run 405 vs 100-kyr ecc. D. Envelope (red); Filtered Data (blue) for TimeOpt run 405 vs 100-kyr ecc. E. Fit: r²[envelope] (red) and r²[power] (grey) for TimeOpt run 100-kyr eccentricity versus precession. F. Optimal Fit: r²[opt] for TimeOpt run 100-kyr eccentricity versus precession. G. Envelope (red); Reconstructed Ecc. Model (black) for TimeOpt run 100-kyr ecc. versus precession. H. Envelope (red); Filtered Data (blue) for TimeOpt run 100-kyr eccentricity versus precession.

3.2 Extract the 405-kyr cycle and generate a sedimentation-rate curve

Based on the TimeOpt results, the 405-kyr eccentricity cycle is extracted from the induration record using a Taner bandpass filter, which spans from 4.86 to 16.2, corresponding to the peak of the optimal sedimentation rate range (1.2-4 cm/ka) as determined by the TimeOpt analysis results. Next, a sedimentation-rate curve is constructed using the minimal tuning technique, in which the distance between two successive peaks of the extracted cycle is set to the duration of the interpreted astronomical cycle. This duration is subsequently used to generate the sedimentation-rate curve (see Figure 4).

Code
induration_filt <-
  taner(
    induration,
    fhigh = 1 / (4.5 * 4.05 ),
    flow = 1 /  (1.2 * 4.05 ),
    roll = 10 ^ 5,
    xmax = 1,genplot=FALSE,verbose = FALSE
  )

induration_filt_min_tun <- minimal_tuning(
  data = induration_filt,
  pts = 5,
  cycle = 405,
  tune_opt = "minmax",
  output = 0,
  genplot = FALSE,
  keep_editable = FALSE
)


layout.matrix <- matrix(c(1, 2), nrow = 2, ncol = 1)
graphics::layout(mat = layout.matrix,
                 heights = c(1,1,1,1),
                 widths = c(1))
par(mar = c(4, 4, 1, 1))

plot(
  x = induration[, 1],
  y = induration[, 2],
  type = "l",
  main = "Induration and 405-kyr ecc. cycle depth domain",
  xlab = "position (m)",
  ylab = "Induration"
)
lines(induration_filt, col = "red",lwd=2)

usr <- par("usr")
text(
  usr[1],
  usr[4] - 0.2 * (usr[4] - usr[3]),
  "A",
  pos = 4,
  font = 2,
  cex = 1.5
)


plot(
  x = induration_filt_min_tun[, 1],
  y = induration_filt_min_tun[, 3],
  type = "l",
  xlab = "position (m)",
  ylab = "cm/kyr (kyr)",
  main = "sedimentation rate plot"
)
usr <- par("usr")
text(
  usr[1],
  usr[4] - 0.2 * (usr[4] - usr[3]),
  "B",
  pos = 4,
  font = 2,
  cex = 1.5
)

Figure 4. Minimal tuning. A. Induration record and the extracted 405-kyr eccentricity cycle. B. Sedimentation rate and sedimentation rate (cm/kyr) based on the minimal tuning.

3.3 Tracking the period (m) of the 405-kyr eccentricity cycle in the the CWT scalogram

The sedimentation rate curve attained from the minimal tuning was then used to convert cm/kyr to period (m) of different astronomical cycles. These cycles were then plotted on top of the CWT scalogram (see figure 4). We can see that the curves plot through areas of high spectral power corresponding to astronomical cycles that should be present according to the TimeOpt-derived sedimentation rate modelling runs. Next, the period (m) of the 405-kyr eccentricity cycle was tracked in the CWT.

Code
induration_wt <- analyze_wavelet(
  data = induration,
  dj = 1/250,
  lowerPeriod = 0.05,
  upperPeriod = 35,
  verbose = FALSE,
  omega_nr = 6,
  pval = FALSE,
  n_simulations = 10,
  run_multicore = FALSE
)

This code must be run manually in an interactive R session. It requires selecting points interactively

induration_track <- track_period_wavelet(induration_wt)

induration_track_comp <- completed_series( tracked_curve = induration_track, wavelet = induration_wt )

induration_track_comp <- noLow( induration_track[, c(1,2)], output = 2, smooth = 0.1 )

write.csv(induration_track_comp, “induration_track_comp.csv”)

Code
# Load the tracked period curve 
induration_track_comp <- read.csv(
  "https://raw.githubusercontent.com/stratigraphy/Kosov-cyclostrat/main/induration_track_comp.csv"
)
#induration_track_comp <- read.csv("induration_track_comp.csv")


induration_track_comp <- induration_track_comp[,c(2,3)]

plot_wavelet(induration_wt,add_avg = TRUE,keep_editable = TRUE,
              dev_new = FALSE,
             periodlab = "Period (metres)",
  x_lab = "Position (metres)")

lines(induration_filt_min_tun[,1],
      log2(induration_filt_min_tun[,3]*4.05),
      col=adjustcolor("red", alpha.f=1), lwd=2)

lines(induration_track_comp[,1],
      log2(induration_track_comp[,2]/4.05*1.1),
      col=adjustcolor("purple", alpha.f=0.5), lwd=10, lty=1)

lines(induration_track_comp[,1],
      log2(induration_track_comp[,2]/4.05*0.30),
      col=adjustcolor("blue", alpha.f=0.5), lwd=10, lty=1)

lines(induration_track_comp[,1],
      log2(induration_track_comp[,2]/4.05*0.19),
      col=adjustcolor("darkgreen", alpha.f=0.5), lwd=10, lty=1)

lines(induration_track_comp[,1],
      log2(induration_track_comp[,2]),
      col=adjustcolor("black", alpha.f=0.5), lwd=10, lty=1)

Figure 5. The Continuous Wavelet Transform (CWT) of the induration record extracted from the litholog of Frýda et al. (2020) and the period (m) of astronomical cycles based on the sedimentation rates attained from the minimal tuning and the tracked period (m) of the 405-kyr eccentricity cycle. Black dotted = tracked period (m) the 405-kyr eccentricity cycle Red = the 405-kyr eccentricity cycle (minimal tuning based) Purple = the 100-kyr eccentricity cycle (minimal tuning based) Blue = the obliquity cycle (minimal tuning based) Dark green = the precession cycle (minimal tuning based)

3.4 Combine the tracked period (m) and a tie-point curve to create an anchored astrochronoloy

The tracked period (m) curve of the 405-kyr eccentricity cycle was then be used to create a floating astrochronology with an assigned uncertainty based on the analytical uncertainty of the Wavelet (Arts et al., 2024). This floating age model with uncertainty was then anchored to the astrochronologically calibrated age of the Ludlow-Pridoli boundary (423.03 ±0.53 Ma) (Arts et al., 2024). This anchored numerical age model could then be used to assign ages and durations (with uncertainty) to the record of the Kosov quarry section and its subsections

Code
induration_track_comp_unc <- wavelet_uncertainty(
  tracked_cycle = induration_track_comp,
  period_of_tracked_cycle = 405,
  wavelet = induration_wt,
  multi = 1,
  verbose = FALSE,
  genplot_time = TRUE,
  genplot_uncertainty = FALSE,
  genplot_uncertainty_wt = FALSE,
  keep_editable = FALSE,
  palette_name = "rainbow",
  color_brewer = "grDevices")

induration_track_comp_unc_sel <- induration_track_comp_unc[,c(1,3,5)]

proxy_list <- list(induration)
id <- c("age_prid_bound")
ages <- c(423030)
ageSds <- c(530/2)
ages_unc_dist <- c("n")
position <- c(22.0)
anchor_thick <- c(0.1)
anchor_thick_unc_dist <- c("u")
bound_age <-
 as.data.frame(
   cbind(
     id,
     ages,
     ageSds,
     ages_unc_dist,
     position,
     anchor_thick,
     anchor_thick_unc_dist
   )
 )

gap_dur = c(NULL)
gap_unc = c(NULL)
gap_depth = c(NULL)
gap_unc_dist = c(NULL)

gap_constraints_none <-
 as.data.frame(cbind(gap_dur, gap_unc, gap_depth, gap_unc_dist))

cycles_checks <- c(405,110,33,19)
uncer_cycles_checks <- c(20.25,35,7,6)


# curve2time_unc_anchor_res <-
#   curve2time_unc_anchor(
#  age_constraint = bound_age ,
#  tracked_cycle_curve = induration_track_comp_unc_sel,
#  tracked_cycle_period = 405 ,
#  tracked_cycle_period_unc = 6,
#  tracked_cycle_period_unc_dist = "n" ,
#  n_simulations = 10000,
#  gap_constraints = NULL,
#  proxy_data = proxy_list,
#  cycles_check = cycles_checks,
#  uncer_cycles_check = uncer_cycles_checks ,
#  max_runs = 10000 ,
#  run_multicore = TRUE,
#  verbose = TRUE ,
#  genplot = FALSE,
#  keep_nr = 2,
#  keep_all_time_curves = FALSE,
#  dj = 1 / 50 ,
#  lowerPeriod = 10 ,
#  upperPeriod = 800 ,
#  omega_nr = 10,
#  seed_nr = 1337,
#  dir = FALSE)
# saveRDS(curve2time_unc_anchor_res, file = "D:/Phd/documents/kosov/curve2time_unc_anchor_res.rds")

This code takes a long time to run

proxy_list <- list(induration) id <- c(“age_prid_bound”) ages <- c(423030) ageSds <- c(530/2) ages_unc_dist <- c(“n”) position <- c(22.0) anchor_thick <- c(0.1) anchor_thick_unc_dist <- c(“u”) bound_age <- as.data.frame( cbind( id, ages, ageSds, ages_unc_dist, position, anchor_thick, anchor_thick_unc_dist ) )

gap_dur = c(NULL) gap_unc = c(NULL) gap_depth = c(NULL) gap_unc_dist = c(NULL)

gap_constraints_none <- as.data.frame(cbind(gap_dur, gap_unc, gap_depth, gap_unc_dist))

cycles_checks <- c(405,110,33,19) uncer_cycles_checks <- c(40.5,30,5,5)

curve2time_unc_anchor_res <- curve2time_unc_anchor( age_constraint = bound_age , tracked_cycle_curve = induration_track_comp_unc_sel, tracked_cycle_period = 405 , tracked_cycle_period_unc = 6, tracked_cycle_period_unc_dist = “n” , n_simulations = 10000, gap_constraints = NULL, proxy_data = proxy_list, cycles_check = cycles_checks, uncer_cycles_check = uncer_cycles_checks , max_runs = 1000 , run_multicore = TRUE, verbose = TRUE , genplot = FALSE, keep_nr = 2, keep_all_time_curves = FALSE, dj = 1 / 100 , lowerPeriod = 10 , upperPeriod = 800 , omega_nr = 10, seed_nr = 1337, dir = FALSE) saveRDS(curve2time_unc_anchor_res, file = “curve2time_unc_anchor_res.rds”)

Code
curve2time_unc_anchor_res <- readRDS(file = "D:/Phd/documents/kosov/curve2time_unc_anchor_res.rds")

results <- as.data.frame(curve2time_unc_anchor_res[[2]])

kosov_time<- cbind(curve2time_unc_anchor_res[[1]],rowMeans(results))
kosov_sd<- rowSds(as.matrix(results))

induration_time<- tune(induration,kosov_time,extrapolate=F,genplot=F,check=T,verbose=T)
kosov_d13Ccarb_time<- tune(kosov_d13Ccarb,kosov_time,extrapolate=F,genplot=F,check=T,verbose=T)
kosov_d13Corg_time<- tune(kosov_d13Corg,kosov_time,extrapolate=F,genplot=F,check=T,verbose=T)

d13C_tuned <- kosov_d13Ccarb_time
d13C_tuned <- linterp(d13C_tuned,1,genplot=FALSE)
d13C_tuned_2 <- d13C_tuned
d13C_tuned <- noLow(d13C_tuned[,c(1,2)],smooth=0.05,2,genplot=FALSE)
diff_d13C <- diff(d13C_tuned[,2])
d13C_tuned[,3] <- c(-1*diff_d13C[1],-1*diff_d13C)
Code
plot(kosov_time[,1],kosov_time[,2]/1000,xlab="postion (m)",ylab="Time (Ma)",type="l",ylim=rev(c(min(kosov_time[,2]-2*kosov_sd)/1000,max(kosov_time[,2]+2*kosov_sd)/1000)))
lines(kosov_time[,1],(kosov_time[,2]+2*kosov_sd)/1000,col="blue")
lines(kosov_time[,1],(kosov_time[,2]-2*kosov_sd)/1000,,col="red")
abline(v=my_data[8,2],lty=3,col="red",lwd=3)

Figure 6. Astrochronological numerival age- depth model.The black line is the mean age-depth curve, and the red and blue lines are time plus and minus two standard deviations (2σ)

3.5 Ages and durations for intervals including uncertainty

The age model provides durations with uncertainty for the chrono, litho-, and event-stratigraphic intervals previously identified in the succession (Frýda and Manda, 2013; Frýda et al., 2021a). The calculated ages, durations, and their associated uncertainties were rounded to the nearest 10 kyr to align with the precision of the anchor point.

Code
kosov_time<- cbind(curve2time_unc_anchor_res[[1]],rowMeans(results))
kosov_sd<- rowSds(as.matrix(results))


for(i in 1:nrow(my_data)){

row_nr_1 <- DescTools::Closest(kosov_time[,1],my_data[i,2],which = TRUE)
row_nr_2 <- DescTools::Closest(kosov_time[,1],my_data[i,3],which = TRUE)

my_data[i,4] <- round(mean(as.matrix(results[row_nr_1,]),na.rm = TRUE)/10,0)/100

my_data[i,5] <- round(2*sd(as.matrix(results[row_nr_1,]),na.rm = TRUE)/10,0)/100

my_data[i,6]<- round(mean(as.matrix(results[row_nr_2,]),na.rm = TRUE)/10,0)/100

my_data[i,7] <- round(2*sd(as.matrix(results[row_nr_2,]),na.rm = TRUE)/10,0)/100

my_data[i,8] <- round(mean(as.matrix(na.omit(results[row_nr_1,]-results[row_nr_2,])))/10,0)*10

my_data[i,9] <- round(2*sd(results[row_nr_2,]-results[row_nr_1,],na.rm = TRUE)/10,0)*10

}

for(i in 1:nrow(my_data)){
  for( j in 8:ncol(my_data)){
    if (my_data[i,j]<=10){
    my_data[i,j] <- 10
  }}}

bottom_record <-  kosov_time[DescTools::Closest(kosov_time[,1],my_data[1,2],which=TRUE),2]
base_pridoli <-  kosov_time[DescTools::Closest(kosov_time[,1],my_data[8,2],which=TRUE),2]
Top_record <-  kosov_time[DescTools::Closest(kosov_time[,1],my_data[1,3],which=TRUE),2]
base_LAU<-  kosov_time[DescTools::Closest(kosov_time[,1],my_data[4,2],which=TRUE),2]
base_S_zone<-  kosov_time[DescTools::Closest(kosov_time[,1],my_data[5,2],which=TRUE),2]
base_F_zone<-  kosov_time[DescTools::Closest(kosov_time[,1],my_data[6,2],which=TRUE),2]
Top_F_zone<-  kosov_time[DescTools::Closest(kosov_time[,1],my_data[6,3],which=TRUE),2]
base_event <-  kosov_time[DescTools::Closest(kosov_time[,1],my_data[2,2],which=TRUE),2]
# Combine uncertainty with age and duration values
my_data$age_bottom_combined <- paste0(
  round(my_data[,4], 3), " ± ", round(my_data[,5], 2)
)

my_data$age_top_combined <- paste0(
  round(my_data[,6], 3), " ± ", round(my_data[,7], 2)
)

my_data$duration_combined <- paste0(
  round(my_data[,8], 0), " ± ", round(my_data[,9], 0)
)

# Reorder the data frame BEFORE passing to gt
my_data_out <- my_data |>
  dplyr::select(
    Interval,
    position_bottom,
    position_top,
    age_bottom_combined,
    age_top_combined,
    duration_combined
  )

# Create gt table
tab <- gt(my_data_out) |>
  cols_label(
    Interval             = "Interval",
    position_bottom      = "position (m) bottom",
    position_top         = "position (m) top",
    age_bottom_combined  = "Age bottom (Ma) ±2sd ",
    age_top_combined     = "Age top (Ma) ±2sd ",
    duration_combined    = "duration (kyr) ±2sd"
  ) |>
  tab_header(
    title = "Table 1. Ages and durations based on the anchored astrochronological age model"
  )



tab
Table 1. Ages and durations based on the anchored astrochronological age model
Interval position (m) bottom position (m) top Age bottom (Ma) ±2sd Age top (Ma) ±2sd duration (kyr) ±2sd
Entire record -7.2 25.0 424.6 ± 0.56 422.8 ± 0.53 1800 ± 190
Lau Biogeochemical Event -0.4 20.0 424.06 ± 0.55 423.19 ± 0.53 870 ± 90
LKB Event (trace metal peak) -0.4 0.0 424.06 ± 0.55 424.03 ± 0.55 30 ± 10
R-zone 0.0 1.4 424.03 ± 0.55 423.94 ± 0.54 90 ± 10
S-Zone 1.4 11.6 423.94 ± 0.54 423.64 ± 0.54 310 ± 30
F-Zone 11.6 20.0 423.64 ± 0.54 423.19 ± 0.53 440 ± 50
Ludlow part section 0.0 22.0 424.03 ± 0.55 423.03 ± 0.53 1000 ± 110
Pridoli part section 22.0 25.0 423.03 ± 0.53 422.8 ± 0.53 230 ± 20

3.6 Extract and plot the record and the astronomical cycles in the time domain

The age model was used to convert the induration and δ13Ccarb record to the (numerical) time domain. Next, the CWT was performed on the tuning induration record. Peaks corresponding to the 8.5-kyr half precession, ~16-kyr precession, 28-kyr obliquity,50-kyr eccentricity, 100-kyr eccentricity, 200-kyr eccentricity and 405-kyr eccentricity cycle can be observed in the CWT scalogram (see Figure 7).Next the precession, obliquity, 100-kyr eccentricity and 405-kyr eccentricity were also extracted from the tuned induration record (see Figure 7). The Hilbert transform was applied to the precession, obliquity and 100-kyr eccentricity cycles extracted from the tuned induration record. The 405-kyr eccentricity cycle was also extracted from the Hilbert transform-derived amplitude record of the 100-kyr eccentricity and compared to the 405-kyr eccentricity directly extracted from the record, showing an anti-phased relationship (see Figure 7).

Code
induration_time <- linterp(induration_time,genplot=FALSE)
induration_time_wt <- analyze_wavelet(data = induration_time,
                                      dj=1/200,upperPeriod = 1000,
                                      lowerPeriod = 5,omega_nr = 6)
induration_filt_time_405 <-
  taner(
    induration_time,
    fhigh = 1 / 505,
    flow = 1 /  305,
    roll = 10 ^ 20,
    xmax = 1/50,genplot=FALSE
  )

induration_filt_time_110 <-
  taner(
    induration_time,
    fhigh = 1 / 150,
    flow = 1 /  85,
    roll = 10 ^ 20,
    xmax = 1/50,genplot=FALSE
  )

induration_filt_time_110_hilb <-
  hilbert(induration_filt_time_110,genplot=FALSE
  )


induration_filt_time_110_hilb_405 <-
  taner(
    induration_filt_time_110_hilb,
    fhigh = 1 / 505,
    flow = 1 /  305,
    roll = 10 ^ 20,
    xmax = 1/50,genplot=FALSE
  )



induration_filt_time_obl <-
  taner(
    induration_time,
    fhigh = 1 / (33.5+7.5),
    flow = 1 / (33.5-7.5),
    roll = 10 ^ 10,
    xmax = 1/10,genplot=FALSE
  )
induration_filt_time_obl_hilb <- hilbert(induration_filt_time_obl,genplot=FALSE)
induration_filt_time_obl_hilb_173 <-
  taner(
    induration_filt_time_obl_hilb,
    fhigh = 1 / (173+20),
    flow = 1 / (173-20),
    roll = 10 ^ 20,
    xmax = 1/100,genplot=FALSE
  )

induration_filt_time_prec <-
  taner(
    induration_time,
    fhigh = 1 / (20+5),
    flow = 1 / (20-5),
    roll = 10 ^ 20,
    xmax = 1/10,genplot=FALSE
  )
induration_filt_time_prec_hilb <- hilbert(induration_filt_time_prec,genplot=FALSE)
induration_filt_time_prec_hilb_110 <-
  taner(
    induration_filt_time_prec_hilb,
    fhigh = 1 / 150,
    flow = 1 /  85,
    roll = 10 ^ 20,
    xmax = 1/75,genplot=FALSE
  )
Code
Ludlow_col <- geo_col(name = "Ludlow")
Pridoli_col <- geo_col(name = "Pridoli")
    
Top_record  <- max(kosov_time[,2])
bottom_record <- min(kosov_time[,2])
  
ylims <- rev(c(min(kosov_time[, 2])-5,max(kosov_time[,2])+5))
vert_lines <-   c(8.5,16,28,50,105,200,405)



induration_time <- linterp(induration_time,genplot=FALSE)
induration_time_wt <- analyze_wavelet(data = induration_time,
                                      dj=1/200,upperPeriod = 1000,
                                      lowerPeriod = 5,omega_nr = 6)

{
layout.matrix <- matrix(c(rep(0,4),1,rep(0,4),seq(2,10,by=1)),
                        nrow = 2,
                        ncol = 9 ,
                        byrow = TRUE)

graphics::layout(mat = layout.matrix,
                 heights = c(0.25,1),
                 # Heights of the two rows
                 widths = c(rep(c(2,1,3,3,6,3,3,3),2)))

par(mar = c(0, 0.5, 1, 0.5))


wavelet <- induration_time_wt
xlim_vals = rev(c(min(wavelet$x), max(wavelet$x)))
ylim_vals = c(5,1000)
n.levels <- 100

color_brewer_Sel <-
  "grDevices::rainbow(n=n.levels, start = 0, end = 0.7)"
key.cols <- rev(eval(parse(text = color_brewer_Sel)))
power_max_mat.levels = quantile(wavelet$Power, probs = seq(
  from = 0,
  to = 1,
  length.out = n.levels + 1
))


periodlab <- "period (kyr)"
main = NULL

plot(
  wavelet$Period,
  wavelet$Power.avg,
  typ = "l",
  log = "x",
  xlim = ylim_vals,
  xaxt = 'n',
  xaxs = "i"
)

abline(v = vert_lines, col=adjustcolor("black", alpha.f=0.4), lwd=6, lty=1)


text(x=6.5,
     y=0.01,
  labels="E",
  cex =2
)


par(mar = c(4, 4, 0, 0))


plot(
  x = c(0, 1),
  y = c(max(kosov_time[, 1]) + 5, min(kosov_time[, 1]) -
          5),
  col = "white",
  xlab = "",
  ylab = "Age (Ma)",
  xaxt = "n",
  xaxs = "i",
  yaxs = "i",
  yaxt= "n",
  ylim = ylims,
)    

par(xpd = NA)
title(main = "A", line = 1, cex.main = 1.5)
par(xpd = FALSE) # reset

a <- round(c(max(kosov_time[, 2]), min(kosov_time[, 2]))/1000)

axis(2, at = rev(1000*seq(a[1],
                          a[2]-0.25, by = -0.25)),
     labels = rev(seq(a[1],
                      a[2]-0.25, by = -0.25)))

Hmisc::minor.tick(nx = 0, ny = 10,   # Ticks density
                  tick.ratio = 0.5) # Ticks size


polygon(
    y = c(
      bottom_record,
      bottom_record,
      base_pridoli,
      base_pridoli
    ),
    x = c(0, 1, 1, 0),
    col = Ludlow_col
  )

    polygon(
    y = c(
      base_pridoli,
      base_pridoli,
      Top_record,
      Top_record
    ),
    x = c(0, 1, 1, 0),
    col = Pridoli_col
  )

    text(
    labels = "Ludlow",cex=2,
    x = 0.5,
    srt = 270,
    y = (bottom_record + base_pridoli) / 2
  )

    text(
    labels = "Pridoli",cex=2,
    x = 0.5,
    srt = 270,
    y = (Top_record + base_pridoli) / 2
  )
 
   
par(mar = c(4, 0.5, 0, 0.5))

    
plot(
  x = c(0, 1),
  y = c(max(kosov_time[, 1]) + 5, min(kosov_time[, 1]) -
          5),
  col = "white",
  xlab = "",
  ylab = "",
  xaxt = "n",
  xaxs = "i",
  yaxs = "i",
  yaxt = "n",
  ylim = ylims
)    

par(xpd = NA)
title(main = "B", line = 1, cex.main = 1.5)
par(xpd = FALSE) # reset

polygon(
    y = c(
      base_LAU,
      base_LAU,
      base_S_zone,
      base_S_zone
    ),
    x = c(0, 1, 1, 0),
    col = "lightgrey"
  )
    text(
      labels = "R-zone",cex=1,
    x = 0.5,
    srt = 270,
    y = (base_LAU + base_S_zone) / 2
  )

polygon(
    y = c(
      base_S_zone,
      base_S_zone,
      base_F_zone,
      base_F_zone
    ),
    x = c(0, 1, 1, 0),
    col = "lightgrey"
  )
    text(
    labels = "S-zone",cex=2,
    x = 0.5,
    srt = 270,
    y = (base_S_zone + base_F_zone) / 2
  )

polygon(
    y = c(
      base_F_zone,
      base_F_zone,
      Top_F_zone,
      Top_F_zone
    ),
    x = c(0, 1, 1, 0),
    col = "lightgrey"
  )
    text(
    labels = "F-zone",cex=2,
    x = 0.5,
    srt = 270,
    y = (Top_F_zone + base_F_zone) / 2
  )

 plot(
  d13C_tuned_2[, 2],
  d13C_tuned_2[, 1],
  type = "l",
  ylim = ylims,
  xlab =  "δ13Ccarb",
  yaxt = "n",
  xaxs = "i",
  xlim = c(min(d13C_tuned_2[, 2]) - 0.15, max(d13C_tuned_2[, 2]) *
             1.1),
  yaxs = "i",col="lightgreen"
)

 par(xpd = NA)
title(main = "C", line = 1, cex.main = 1.5)
par(xpd = FALSE) # reset
 
 
polygon(
  x =
    c(
      min(d13C_tuned_2[, 2]) - 0.15,
      max(d13C_tuned_2[, 2]) * 1.1,
      max(d13C_tuned_2[, 2]) * 1.1,
      min(d13C_tuned_2[, 2]) - 0.15
    ),
  y = c(
    Top_F_zone,
    Top_F_zone,
    base_LAU,
    base_LAU
  ),
  col = rgb(0, 0, 0, 0.25)
)

 text(
    labels = "Lau δ13Ccarb excursion",
    x = 0.5,
    srt = 270,cex=2,
    y = (Top_F_zone + base_LAU) / 2
  )
 
 
abline(h=base_event,col="red",lty=3,lwd=3) 

    text(
    labels = "Onset \n LKB",
    x = 4,cex=2,
    srt = 0,
    y = (base_event + 5)
  ) 

lines(d13C_tuned_2[,c(2,1)],col="lightgreen",lwd=1)
lines(d13C_tuned[,c(2,1)],col="darkgreen",lwd=1)



plot(
  induration_time[, 2],
  induration_time[, 1],
  type = "l",
  ylim =ylims,
  yaxs = "i",
  yaxt = "n",
  xlab = "Induration"
)

par(xpd = NA)
title(main = "D", line = 1, cex.main = 1.5)
par(xpd = FALSE) # reset
image(
  y = wavelet$x,
  x = wavelet$axis.2,
  z = (wavelet$Power),
  col = key.cols,
  breaks = power_max_mat.levels,
  useRaster = TRUE,
  xlab = periodlab,
  ylab = "",
  #axes = FALSE,
  #yaxt = "n" ,
  xaxt = "n" ,
  yaxt = "n" ,
  main = main,
  ylim = ylims,
  xlim = log2(ylim_vals)
)

periodtck = 0.02
periodtcl = 0.5
main = NULL
lwd = 2
lwd.axis = 1
box(lwd = lwd.axis)

period.tick = unique(trunc(wavelet$axis.2))
period.tick <- period.tick[period.tick >= log2(ylim_vals[1])]
period.tick <- period.tick[period.tick <= log2(ylim_vals[2])]
nrs <- seq(period.tick[1], length(period.tick), by = 2)
period.tick <- nrs
period.tick[period.tick < log2(wavelet$Period[1])] = NA
period.tick = na.omit(period.tick)
period.tick.label = 2 ^ (period.tick)

axis(
  1,
  lwd = lwd.axis,
  at = period.tick,
  labels = NA,
  tck = periodtck,
  tcl = periodtcl
)


mtext(
  period.tick.label,
  side = 1,
  at = period.tick,
  las = 2,
  line = par()$mgp[2] - 0.5,
  font = par()$font.axis,
  cex = par()$cex.axis
)

abline(v = log2(vert_lines), col=adjustcolor("black", alpha.f=0.4), lwd=6, lty=1)

plot(
  x = induration_filt_time_405[, 2],
  y = induration_filt_time_405[, 1],
  type = "l",
  ylim =ylims,
,
  yaxs = "i",
  yaxt = "n",
  xlab = "405-kyr ecc"
)

par(xpd = NA)
title(main = "F", line = 1, cex.main = 1.5)
par(xpd = FALSE) # reset

lines(x = (induration_filt_time_110_hilb_405[, 2]
            -mean(induration_filt_time_110_hilb_405[, 2]))*2+mean(induration_filt_time_405[,2]), y = induration_filt_time_110_hilb_405[, 1], col =
        "red")

plot(
  x = induration_filt_time_110[, 2],
  y = induration_filt_time_110[, 1],
  type = "l",
  ylim =ylims,
,
  yaxs = "i",
  yaxt = "n",
  xlab = "100-kyr ecc")

par(xpd = NA)
title(main = "G", line = 1, cex.main = 1.5)
par(xpd = FALSE) # reset

lines(
  induration_filt_time_110_hilb[, 2] + mean(induration_filt_time_110[, 2]),
  induration_filt_time_110_hilb[, 1],
  lwd = 1.5,col="red"
)



plot(
  x = induration_filt_time_prec[, 2],
  y = induration_filt_time_prec[, 1],
  type = "l",
  ylim =ylims,
,
  yaxs = "i",
  yaxt = "n",
  xlab = "Precession"
)

par(xpd = NA)
title(main = "H", line = 1, cex.main = 1.5)
par(xpd = FALSE) # reset

lines(
  induration_filt_time_prec_hilb[, 2] + mean(induration_filt_time_prec[, 2]),
  induration_filt_time_prec_hilb[, 1],
  lwd = 1.5,col="red"
)


plot(
  x = induration_filt_time_obl[, 2],
  y = induration_filt_time_obl[, 1],
  type = "l",
  ylim =ylims,
,
  yaxs = "i",
  yaxt = "n",
  xlab = "Obliquity"
)

par(xpd = NA)
title(main = "I", line = 1, cex.main = 1.5)
par(xpd = FALSE) # reset

lines(
  induration_filt_time_obl_hilb[, 2] + mean(induration_filt_time_obl[, 2]),
  induration_filt_time_obl_hilb[, 1],
  lwd = 1.5,col="red"
)
}

Figure 7. Induration proxy record in the time domain, including the wavelet scalogram of the induration record and astronomical cycles extracted from said record. A Stages. B. Event zone subdivision C. The δ13Ccarb record. D. The Induration record. E. Wavelet scalogram of the induration record with the average spectral power on top. The black vertical lines in the wavelet scalograms are durations of known astronomical cycles. From left to right, these cycles are the 8.5-kyr half precession, ~16-kyr precession, 28-kyr obliquity,50-kyr eccentricity, 100-kyr eccentricity, 200-kyr eccentricity and the 405-kyr eccentricity cycle. F. The black line is a 405-kyr eccentricity cycle extracted from the Induration record. The red line is the 405-kyr eccentricity cycle extracted from the Hilbert transform of the 100-kyr eccentricity cycle of the Induration record. G. The 100-kyr eccentricity cycle was extracted from the Induration record (black line) and the Hilbert transform of the 100-kyr eccentricity cycle (red line). H.) Obliquity cycle extracted from the Induration record (black line), and the Hilbert transform of the obliquity cycle (red line). I. The precession cycle extracted from the Induration record (black line) and the Hilbert transform of the precession cycle (red line).

3.7 The phase relationships

The 405-kyr eccentricity cycle was extracted from the induration record and extracted from the Hilbert transform of the 100-kyr eccentricity cycle of the induration record. The 100-kyr eccentricity cycle was extracted from the induration record and extracted from the Hilbert transform of the precession cycle of the induration record. The relationship between the 405-kyr eccentricity cycle directly extracted from the record and the one obtained from the Hilbert transform of the 100-kyr cycle shows an anti-phased relationship. The relationship between the 100-kyr eccentricity cycle directly extracted from the induration record and that obtained from the Hilbert transform of the precession cycle demonstrates a generally anti-phased relationship as well (see Figure 8).

Code
ylims <- c(min(kosov_time[, 2])-5,max(kosov_time[,2])+5)
ylims <- rev(ylims/1000)
layout.matrix <- matrix(c(1,2),
                        nrow = 1,
                        ncol = 2 ,
                        byrow = TRUE)

graphics::layout(mat = layout.matrix,
                 heights = c(1,1),
                 # Heights of the two rows
                 widths = c(1,1))

par(mar = c(4, 4, 4, 2))

plot(
  x = induration_filt_time_405[, 2],
  y = induration_filt_time_405[, 1]/1000,
  type = "l",
  ylim =ylims,
  xlab = "405-kyr ecc",col="black",
  ylab = "Age (Ma)",
  main="A"
)

lines(x = (induration_filt_time_110_hilb_405[, 2]
            -mean(induration_filt_time_110_hilb_405[, 2]))*2+mean(induration_filt_time_405[,2]), y = induration_filt_time_110_hilb_405[, 1]/1000, col =
        "red")

plot(
  x = induration_filt_time_110[, 2],
  y = induration_filt_time_110[, 1]/1000,
  type = "l",
  ylim =ylims,
  xlab = "100-kyr ecc",col="black",
  ylab =  "Age (Ma)",
  main="B"
)

lines(x = (induration_filt_time_prec_hilb_110[, 2]
            -mean(induration_filt_time_prec_hilb_110[, 2]))*2+mean(induration_filt_time_110[,2]), y = induration_filt_time_prec_hilb_110[, 1]/1000, col =
        "red")

Figure 8. Astronomical cycles extracted from the tuned induration record. A. The black line is a 405-kyr eccentricity cycle extracted from the Induration record. The red line is the 405-kyr eccentricity cycle extracted from the Hilbert transform of the 100-kyr eccentricity cycle of the Induration record. B. The black line is a 100-kyr eccentricity cycle extracted from the Induration record. The red line is the 100-kyr eccentricity cycle extracted from the Hilbert transform of the precession cycle of the Induration record.

3.8 Rate of change in the δ13Ccarb curve

The rate of change (‰/kyr) of the δ13Ccarb curve was calculated from the LOWESS regression applied to the δ¹³Ccarb record. The regression was applied to minimise the influence of short-term local noise (Figure 9). The rate of change (‰/kyr) reaches a maximum of 0.09 (‰/kyr) during the onset of the Lau Biogeochemical Event. The rate of change (‰/kyr) record contains a strong imprint of the 100-kyr eccentricity cycle, which was subsequently extracted from the record and plotted next to the 100-kyr eccentricity cycle extracted from the induration record. During the Lau, the rate of change (‰/kyr) recorded its largest amplitude variations. The 100-kyr eccentricity cycle extracted from the rate of change (‰/kyr) record is generally anti-phased with the 100-kyr eccentricity cycle extracted from the induration record.

Code
d13C_tuned_diff_110 <-
  astrochron::taner(
     astrochron::linterp(d13C_tuned[,c(1,3)],genplot=FALSE,verbose=FALSE),
    fhigh = 1 / 150,
    flow = 1 /  80,
    roll = 10 ^ 6,
    xmax = 1/50,
    genplot=FALSE
  )
kosov_d13Ccarb_time_110 <-
  astrochron::taner(
    astrochron::noLow(astrochron::linterp(kosov_d13Ccarb_time[,c(1,2)],    genplot=FALSE
),smooth=0.25,1,    genplot=FALSE),
    fhigh = 1 / 150,
    flow = 1 /  80,
    roll = 10 ^ 6,
    xmax = 1/50,
        genplot=FALSE

  )

layout.matrix <- matrix(c(1, 2,3,4,5,6), nrow = 1, ncol = 6)
graphics::layout(mat = layout.matrix,
                 heights = c(1,1,1,1,1,1), # Heights of the two rows
                 widths = c(1,1,2,2,2,2)) # Widths of the two columns

par(mar = c(4, 4, 2, 0))

a <- round(c(max(kosov_time[, 2]), min(kosov_time[, 2]))/1000)

ylims <- c(min(kosov_time[, 2])-5,max(kosov_time[,2])+5)
ylims <- rev(ylims)

plot(
  x = c(0, 1),
  y = c(max(kosov_time[, 1]) + 5, min(kosov_time[, 1]) -
          5),
  col = "white",
  xlab = "",
  ylab = "Age (Ma)",
  xaxt = "n",
  xaxs = "i",
  yaxs = "i",
  yaxt= "n",
  ylim = ylims,
  main="A"
)    

axis(2, at = rev(1000*seq(a[1],
                          a[2]-0.25, by = -0.25)),
     labels = rev(seq(a[1],
                      a[2]-0.25, by = -0.25)))

Hmisc::minor.tick(nx = 0, ny = 10,   # Ticks density
                  tick.ratio = 0.5) # Ticks size

polygon(
    y = c(
      bottom_record,
      bottom_record,
      base_pridoli,
      base_pridoli
    ),
    x = c(0, 1, 1, 0),
    col = Ludlow_col
  )

    polygon(
    y = c(
      base_pridoli,
      base_pridoli,
      Top_record,
      Top_record
    ),
    x = c(0, 1, 1, 0),
    col = Pridoli_col
  )

    text(
    labels = "Ludlow",
    x = 0.5,
    srt = 270,
    y = (bottom_record + base_pridoli) / 2
  )

    text(
    labels = "Pridoli",
    x = 0.5,
    srt = 270,
    y = (Top_record + base_pridoli) / 2
  )
 
   
par(mar = c(4, 0.5, 2, 0.5))

plot(
  x = c(0, 1),
  y = c(max(kosov_time[, 1]) + 5, min(kosov_time[, 1]) -
          5),
  col = "white",
  xlab = "",
  ylab = "",
  xaxt = "n",
  xaxs = "i",
  yaxs = "i",
  yaxt = "n",
  ylim = ylims,
   main="B"
)    


polygon(
    y = c(
      base_LAU,
      base_LAU,
      base_S_zone,
      base_S_zone
    ),
    x = c(0, 1, 1, 0),
    col = "lightgrey"
  )
    text(
    labels = "R-zone",
    x = 0.5,
    srt = 0,
    y = (base_LAU + base_S_zone) / 2
  )

polygon(
    y = c(
      base_S_zone,
      base_S_zone,
      base_F_zone,
      base_F_zone
    ),
    x = c(0, 1, 1, 0),
    col = "lightgrey"
  )
    text(
    labels = "S-Zone",
    x = 0.5,
    srt = 0,
    y = (base_S_zone + base_F_zone) / 2
  )

polygon(
    y = c(
      base_F_zone,
      base_F_zone,
      Top_F_zone,
      Top_F_zone
    ),
    x = c(0, 1, 1, 0),
    col = "lightgrey"
  )
    text(
    labels = "F-zone",
    x = 0.5,
    srt = 0,
    y = (Top_F_zone + base_F_zone) / 2
  )
    
    
plot(
  induration_time[, 2],
  induration_time[, 1],
  type = "l",
  ylim =ylims,
  yaxs = "i",
  yaxt = "n",
  xlab = "Induration",
   main="C"

)
    
plot(
  d13C_tuned_2[, 2],
  d13C_tuned_2[, 1],
  type = "l",
  ylim = ylims,
  xlab =  "δ13Ccarb",
  yaxt = "n",
  xaxs = "i",
  xlim = c(min(d13C_tuned_2[, 2]) - 0.15, max(d13C_tuned_2[, 2]) *
             1.1),
  yaxs = "i",col="lightgreen",main="D"
)

polygon(
  x =
    c(
      min(d13C_tuned_2[, 2]) - 0.15,
      max(d13C_tuned_2[, 2]) * 1.1,
      max(d13C_tuned_2[, 2]) * 1.1,
      min(d13C_tuned_2[, 2]) - 0.15
    ),
  y = c(
    Top_F_zone,
    Top_F_zone,
    base_LAU,
    base_LAU
  ),
  col = rgb(0, 0, 0, 0.25)
)

 text(
    labels = "Lau  δ13Ccarb excursion",
    x = 2,
    srt = 270,
    y = (Top_F_zone + base_LAU) / 2
  )
 
 
abline(h=base_event,col="red",lty=3,lwd=3) 

    text(
    labels = "Onset \n LKB",
    x = 4,cex=2,
    srt = 0,
    y = (base_event + 5)
  ) 

lines(d13C_tuned_2[,c(2,1)],col="lightgreen",lwd=1)
lines(d13C_tuned[,c(2,1)],col="darkgreen",lwd=1)

plot(d13C_tuned[,c(3,1)],type="l",
     col="darkgreen"
     ,xlab= "δ13Ccarb rate of change (‰/kyr)",
       ylim = ylims,
     ylab="time (kyr)",yaxt="n",
       yaxs = "i", main="E")

lines(d13C_tuned_diff_110[,c(2,1)], col="darkgreen", lwd=2)

plot(induration_filt_time_110[,2], induration_filt_time_110[,1], 
      type="l", yaxt="n",xaxt="n",
      ylim=ylims, col="red", xlab="", lwd=2,
       yaxs = "i", main="F")
par(new=TRUE)
plot(d13C_tuned_diff_110[,c(2,1)], col="darkgreen", lwd=2,ylim=ylims,xlab="",
    type="l",yaxt="n",xaxt="n",  yaxs = "i")

Figure 9. Rate of change (‰/kyr) of the δ13Ccarb record. A Stages. B. Event zone subdivision. C. The Induration record. D. The δ13Ccarb record (light green), and the LOWESS smoothed curve (dark green) E. The δ13Ccarb rate of change record (‰/kyr) and the 100-kyr eccentricity cycle extracted from said record F. The 100-kyr eccentricity cycle extracted from the δ13Ccarb rate of change record (‰/kyr) (dark green) and the 100-kyr eccentricity cycle extracted from the induration record.

3.9 The lag-1 sea-level curve

The lag-1 function was used to calculate the lag-1 autocorrelation coefficient using a windowed analysis Monte-Carlo analysis. The resulting curve serves as a proxy for sea-level changes (Li et al., 2018). The lag-1 does show a few trends (see Figure 10). Before the Lau excursion, the curve is characterized by relatively high values (high sea-level) superimposed by some low-amplitude fluctuations. At the onset of the Lau event, a minor rise is observed, followed by a large drop towards a minimum (low sea-level) during the middle of the Lau δ13Ccarb excursion. Towards the top of the studied succession, the curve returns to higher and more regular values (high stable sea-level). The lag-1 record contains the imprint of the 405-kyr eccentricity cycle, which was extracted and compared to the 405-kyr eccentricity cycle extracted from the induration record. The 405-kyr eccentricity cycle extracted from the rate lag-1 record is generally anti-phased with the 405-kyr eccentricity cycle extracted from the induration record.

Code
induration <- read.csv(
  "https://raw.githubusercontent.com/stratigraphy/Kosov-cyclostrat/main/induration.csv"
)
induration_2 <- sortNave(induration[,c(1,2)],genplot=FALSE)
induration_2[,1] <- seq(25,-7.20,length.out=length(induration_2[,1]))
induration_2 <- linterp(induration_2,0.01,genplot=FALSE)

induration_2[,2] <- induration_2[,2]/max(induration_2[,2])
induration_2[,2] <- sqrt(induration_2[,2])
induration_2[,2] <- induration_2[,2]-min(induration_2[,2])
induration_2[,2] <- induration_2[,2]/max(induration_2[,2])
induration_2[,2] <- induration_2[,2]*5

induration_time_2<- tune(induration_2,kosov_time,extrapolate=F,genplot=F,check=T,verbose=T)
induration_time_2 <- na.omit(induration_time_2)

# induration_time_lag_1_2 <- lag_1(
#   data = induration_time_2,
#   n_sim = 10000,
#   run_multicore = TRUE,
#   win_max = 250,
#   win_min = 10,
#   verbose = TRUE
# )
# write.csv(induration_time_lag_1_2,"D:/Phd/documents/kosov/induration_time_lag_1_2.csv")

This code takes a long time to run so one can also download the results from github induration_time_lag_1 <- lag_1( data = induration_time_2, n_sim = 1000, run_multicore = TRUE, win_max = 300, win_min = 50, verbose = TRUE ) write.csv(induration_time_lag_1,“induration_time_lag_1.csv”)

Code
 induration_time_lag_1 <- read.csv(
   "https://raw.githubusercontent.com/stratigraphy/Kosov-cyclostrat/main/induration_time_lag_1_2.csv"
 )

#induration_time_lag_1 <- read.csv("D:/Phd/documents/kosov/induration_time_lag_1_2.csv")
induration_time_lag_1 <- linterp(induration_time_lag_1[,c(2,3)],verbose=F,genplot=F)

# induration_time_lag_1_wt <- analyze_wavelet(
#   data = induration_time_lag_1,
#   dj = 1/100,
#   lowerPeriod = 50,
#   upperPeriod = 4000,
#   verbose = FALSE,
#   omega_nr = 8,
#   pval = FALSE,
#   n_simulations = 10,
#   run_multicore = FALSE
# )
# 
# plot_wavelet(induration_time_lag_1_wt,add_avg = TRUE,add_abline_h = c(125,220,405,660,1100),dev_new=FALSE)

induration_time_lag_2_110 <- taner(induration_time_lag_1[,c(1,2)],flow=1/80,fhigh=1/135,roll=10^10,xmax=1/50,detrend=TRUE,demean=TRUE,genplot=FALSE)

induration_time_lag_1_405 <-
  taner(induration_time_lag_1[,c(1,2)],
    fhigh = 1 / 505,
    flow = 1 /  305,
    roll = 10 ^ 6,
    xmax = 1/50,detrend=FALSE,demean=FALSE,genplot=FALSE)


layout.matrix <- matrix(c(1, 2,3,4,5,6), nrow = 1, ncol = 6)
graphics::layout(mat = layout.matrix,
                 heights = c(1,1,1,1,1,1), # Heights of the two rows
                 widths = c(1,1,2,2,2,2)) # Widths of the two columns

par(mar = c(4, 4, 2, 0))

ylims <- rev(c(min(kosov_time[, 2])-5,max(kosov_time[,2])+5))

plot(
  x = c(0, 1),
  y = c(max(kosov_time[, 1]) + 5, min(kosov_time[, 1]) -
          5),
  col = "white",
  xlab = "",
  ylab = "Age (Ma)",
  xaxt = "n",
  xaxs = "i",
  yaxs = "i",
  yaxt= "n",
  ylim = ylims,
  main="A"
)    


axis(2, at = rev(1000*seq(a[1],
                          a[2]-0.25, by = -0.25)),
     labels = rev(seq(a[1],
                      a[2]-0.25, by = -0.25)))

Hmisc::minor.tick(nx = 0, ny = 10,   # Ticks density
                  tick.ratio = 0.5) # Ticks size


polygon(
    y = c(
      bottom_record,
      bottom_record,
      base_pridoli,
      base_pridoli
    ),
    x = c(0, 1, 1, 0),
    col = Ludlow_col
  )

    polygon(
    y = c(
      base_pridoli,
      base_pridoli,
      Top_record,
      Top_record
    ),
    x = c(0, 1, 1, 0),
    col = Pridoli_col
  )

    text(
    labels = "Ludlow",
    x = 0.5,
    srt = 270,
    y = (bottom_record + base_pridoli) / 2
  )

    text(
    labels = "Pridoli",
    x = 0.5,
    srt = 270,
    y = (Top_record + base_pridoli) / 2
  )
 
   
par(mar = c(4, 0.5, 2, 0.5))

plot(
  x = c(0, 1),
  y = c(max(kosov_time[, 1]) + 5, min(kosov_time[, 1]) -
          5),
  col = "white",
  xlab = "",
  ylab = "",
  xaxt = "n",
  xaxs = "i",
  yaxs = "i",
  yaxt = "n",
  ylim = ylims,
    main="B"
)    


polygon(
    y = c(
      base_LAU,
      base_LAU,
      base_S_zone,
      base_S_zone
    ),
    x = c(0, 1, 1, 0),
    col = "lightgrey"
  )
    text(
    labels = "R-zone",
    x = 0.5,
    srt = 0,
    y = (base_LAU + base_S_zone) / 2
  )

polygon(
    y = c(
      base_S_zone,
      base_S_zone,
      base_F_zone,
      base_F_zone
    ),
    x = c(0, 1, 1, 0),
    col = "lightgrey"
  )
    text(
    labels = "S-Zone",
    x = 0.5,
    srt = 0,
    y = (base_S_zone + base_F_zone) / 2
  )

polygon(
    y = c(
      base_F_zone,
      base_F_zone,
      Top_F_zone,
      Top_F_zone
    ),
    x = c(0, 1, 1, 0),
    col = "lightgrey"
  )
    text(
    labels = "F-zone",
    x = 0.5,
    srt = 0,
    y = (Top_F_zone + base_F_zone) / 2
  )
    
    
plot(
  induration_time[, 2],
  induration_time[, 1],
  type = "l",
  ylim =ylims,
  yaxs = "i",
  yaxt = "n",
  xlab = "Induration",main="C"

)
    
plot(
  d13C_tuned_2[, 2],
  d13C_tuned_2[, 1],
  type = "l",
  ylim = ylims,
  xlab =  "δ13Ccarb",
  yaxt = "n",
  xaxs = "i",
  xlim = c(min(d13C_tuned_2[, 2]) - 0.15, max(d13C_tuned_2[, 2]) *
             1.1),
  yaxs = "i",col="lightgreen",main="D"
)

polygon(
  x =
    c(
      min(d13C_tuned_2[, 2]) - 0.15,
      max(d13C_tuned_2[, 2]) * 1.1,
      max(d13C_tuned_2[, 2]) * 1.1,
      min(d13C_tuned_2[, 2]) - 0.15
    ),
  y = c(
    Top_F_zone,
    Top_F_zone,
    base_LAU,
    base_LAU
  ),
  col = rgb(0, 0, 0, 0.25)
)

 text(
    labels = "Lau  δ13Ccarb excursion",
    x = 0.5,
    srt = 270,
    y = (Top_F_zone + base_LAU) / 2
  )
 
 
abline(h=base_event,col="red",lty=3,lwd=3) 

    text(
    labels = "Onset \n LKB",
    x = 4,cex=2,
    srt = 0,
    y = (base_event + 5)
  ) 

lines(d13C_tuned_2[,c(2,1)],col="lightgreen",lwd=1)
lines(d13C_tuned[,c(2,1)],col="darkgreen",lwd=1)


plot(induration_time_lag_1[,2], induration_time_lag_1[,1],
     type="l", ylim=ylims,lwd=2,yaxs = "i",yaxt="n",
     col="red", xlab="lag-1 sea-level", ylab="",main="E")

lines(induration_time_lag_1_405[,2], induration_time_lag_1_405[,1],
      type="l", col="brown", xlim=c(0,0.7),lwd=2)


plot(induration_filt_time_405[,2], induration_filt_time_405[,1], 
      type="l", yaxt="n",xaxt="n",
      ylim=ylims, col="blue", xlab="", lwd=2,
       yaxs = "i",main="F")
par(new=TRUE)
plot(induration_time_lag_1_405[,c(2,1)], col="brown", lwd=2,ylim=ylims,xlab="",
    type="l",yaxt="n",xaxt="n",  yaxs = "i")

Figure 10. Rate of change of the δ13Ccarb record. A Stages. B. Event zone subdivision. C. The Induration record. D. The δ13Ccarb record (light green) and the LOWESS smoothed curve (dark green) E. The lag-1 sea-level curve (red) and the 405-kyr eccentricity cycle extracted from said record (brown). F. The 405-kyr eccentricity cycle extracted from the lag-1 curve (brown) and the 405-kyr eccentricity cycle extracted from the Induration record (blue).

4. Discussion

4.1 The astrochronology and the imprint of astronomical cycles

The establishment of the the astrochronological framework for the Kosov quarry section is a new step into the quantification of the tempo of Silurian environmental change to show complete the Silurian astronomical timescale. The new anchored astrochronology provides the first near complete high-resolution temporal calibration which allows us to examine the imprint of astronomical cycles and their role in modulating the Lau Biogeochemical Event. The anchored astrochronological age model was used to assign ages, durations, and associated uncertainties to chrono- and geochronologic units, lithological units, and events in the Kosov quarry section, as tabulated in Table 1. The Lau Biogeochemical Event started at 424.06 ± 0.55 Ma and lasted 0.87 ± 0.03 Myr; in contrast, the LKB Event was as short as 30 ± 10 kyr. The durations and associated uncertainties for the Lau Event and its subdivisions are the first astrochronological constraints available for a near-complete record spanning the Lau Event. The durations and associated uncertainties are therefore the best constrained reference values to date. A previous estimate for the onset of the Lau Event is based on the dating of a hiatus in the Cellon section, spanning 423.83 ± 0.55 Ma to 424.19 ± 0.55 Ma, with the Lau Biogeochemical Event starting within the hiatus (Arts et al., 2024). Our age for the onset of the Lau Biogeochemical Event (424.06 ± 0.55 Ma) falls within this range, supporting alignment with previous results. The uncertainties assigned to durations in the record are around ~15%. Since only one proxy was used, a multi-proxy study might even further reduce the uncertainty in the underlying astrochronology of the Kosov quarry section.

The CWT in the time domain contains spectral peaks which can be linked to the 8.5-kyr half precession, 16-kyr precession, 28-kyr obliquity,50-kyr eccentricity, 100-kyr eccentricity, 200-kyr eccentricity and the 405-kyr eccentricity cycle (see Figure 7). The observation of a whole suite of spectral peaks corresponding to known astronomical cycles in the CWT scalogram validates the underlying age model. The periods of the obliquity and precession cycles are at the shorter end of the typically expected durations for the Silurian, yet remain within the current uncertainty range for the estimated durations of these cycles during the Silurian (Waltham, 2015; Farhat et al., 2022; Wu et al., 2024). The spectral peak of ~55 kyr was observed in the average CWT scalogram. This spectral peak can be either the 55 or 54-kyr eccentricity cycle or the 52.8-kyr obliquity cycle (Laskar et al., 2004; Laskar, 2020, p. 20) (see Figure 7). The spectral power of the 55-kyr cycle is highly variable, indicating that the 55-kyr cycle might be of a transient nature unrelated to astronomical forcing. A 200-kyr cycle can also be identified in the record. Given the strong imprint of the 100-kyr and 405-kyr eccentricity cycles, it is reasonable to interpret the 200-kyr cycle as the seldom observed 200-kyr eccentricity cycle (Hilgen et al. (2020)).

Comparable periodicities have been identified in other Silurian successions, such as the Gotland and Yangtze Platform sections, where 100- and 405-kyr cycles dominate δ¹³C and lithological rhythms [citations]. The slight shortening of precession and obliquity cycles in the Kosov record likely reflects uncertainties in tidal dissipation rates and rotational parameters during the mid-Silurian, consistent with model predictions by Waltham (2015) and Wu et al. (2024).

Since amplitude modulating cycles apply unidirectional to the amplitude of the signal, irrespective of whether the proxy exhibits a positive or negative response to insolation forcing it is possible to infer the phase relationship between astronomical cycles extracted from the proxy record and its relationship with the true phase of the astronomical cycle, as well as its relationship with changes in insolation (Hinnov, 2000; Laskar et al., 2004). In the induration record, the precession and short eccentricity amplitude modulations display an anti-phase relationship (see Figure 8). This indicates that during isolation minima, induration values are at a maximum. The anti-phase behavior between induration and insolation suggests that induration intensity in the Kosov quarry reflects carbonate productivity and terrigenous dilution. During insolation minima, reduced runoff and enhanced carbonate precipitation led to higher induration values, while during insolation maxima, seasonality increases, leading to higher runoff, increased detrital products, and decreased carbonate productivity resulting in low induration values (Mutterlose and Ruffell, 1999; Martinez, 2018).

The consistent imprint of eccentricity and precession cycles in both the induration and δ¹³Ccarb records underscores the pervasive sole that astronomical forcing played in pacing of depositonal setting and carbon cycle during the Lau Event. The established relationship between isolation and induration can also be used to understand the imprint of astronomical cycles in the rate of change ‰/kyr) of the δ¹³Ccarb record and the lag-1 sea-level curve. Since the 100-kyr eccentricity cycle extracted from the rate of change (‰/kyr) record is generally anti-phased with the 100-kyr eccentricity cycle extracted from the induration record, this indicates that the rate of change (‰/kyr) is the highest during eccentricity maxima. The 405-kyr eccentricity cycle extracted from the rate lag-1 record is generally anti-phased with the 405-kyr eccentricity cycle extracted from the induration record, indicating that sea-level is the highest during eccentricity maxima.

4.2 An astronomically paced carbon cycle during the Lau Biogeochemical Event

Quantifying the rate of δ¹³Ccarb change (‰/kyr) provides direct insight into the sensitivity and feedback timescales of the Silurian carbon cycle. Until now, the tempo of major Paleozoic carbon isotope excursions remained largely unconstrained, hindering efforts to compare their dynamics with younger Mesozoic and Cenozoic analogues. The new astrochronologically anchored age model allows for the first robust estimation of δ¹³Ccarb change rates (‰/kyr) across the Lau Event, enabling temporal comparison with other Phanerozoic perturbations (Cramer and Jarvis, 2020).

The new age model shows that the rate of change in the δ¹³Ccarb record, reached maximum values of 0.09‰ per kyr for the Lau Event. These rates were derived from a LOWESS regression of the δ¹³Ccarb curve to minimize the influence of short-term local noise. As such, the values represent a robust regional and potentially global benchmark for the Lau event. The high rate of change in the δ¹³Ccarb record (0.09‰ per kyr) during the Lau Event suggests that the Silurian carbon cycle was highly dynamic during the Lau Event especially so compared to the δ¹³Ccarb rate of change curve calculated from the reference curve (Cramer and Jarvis, 2020) (see Figure 11). Considering the underlying age-model and smoothing our estimates for the δ¹³Ccarb rate of change (‰/kyr) presents a conservative rate for the Lau Event. Since it is now possible to constrain the rate of change, it will be possible to model the Lau Biogeochemical event in the domain. This will enable us to examine whether steady state depth domain models, such as those of Frýda et al. (2020). Incorporating the observed δ¹³C change rates into box or Earth system models will help us constrain the response time of the Silurian carbon reservoir and test whether steady-state carbon cycle models remain valid.

The synchronicity between high δ¹³Ccarb change rates and eccentricity maxima suggests a strong astronomically paced modulation of carbon cycle feedback mechanisms. The 100-kyr eccentricity imprint in the δ¹³Ccarb rate-of-change record during the Lau Event indicates that the carbon cycle was at least partly paced by this cycle. The phase relationship shows that the highest rates of change occurred during eccentricity maxima. This suggests that eccentricity maxima resulted in enhanced organic carbon burial during the Lau Event. The observed phase relationship aligns with a scenario in which eccentricity maxima increased the amplitude of climate precession, resulting in enhanced seasonality (Rossignol-Strick, 1985; Van Os et al., 1994; Sachs and Repeta, 1999; Wang, 2009; Gambacorta et al., 2018). The resulting increase in seasonality intensified the hydrological cycle, increasing physical and chemical weathering, supplying fine-grained sediments and nutrients through enhanced (monsoonal) rainfall. The increased fluvial discharge promoted seasonal productivity blooms, facilitating the accumulation of organic-rich deposits. In contrast, during eccentricity minima, the reduced precession amplitude weakened monsoon intensity, lowered nutrient delivery, and re-established more oxic conditions lower carbon burial and thus lowering the δ¹³Ccarb rate-of-change. The enhanced organic carbon burial during the eccentricity maxima would have led to a drawdown of atmospheric CO₂, promoting global cooling and potentially contributing to glacial expansion during the Lau Biogeochemical Evant (Frýda et al., 2021b). The observed coupled coupling between orbital forcing, carbon burial, and climatic feedbacks thus can thus provide a causal linkg between eccentricity forcing, oceanic anoxia and the onset of the Mid-Ludfordian Glaciation Frýda et al. (2021b)].

Code
# Load the data --------------------------------------------------------
Ciso <- read_excel("D:/Phd/documents/Warm_anoxia/WholeCIso_Neoprotero_Permian.xlsx")

CisoPZ <- filter(Ciso, Age != "Neoprotero")

dCiso_kyr <- cbind((Ciso$Time * 1000) + 250, Ciso$dCiso)
dCiso_kyr <- sortNave(dCiso_kyr, genplot = FALSE, verbose = FALSE)
dCiso_kyr <- linterp(dCiso_kyr, genplot = FALSE, verbose = FALSE)

time_kyr <- dCiso_kyr[, 1]
d13C <- dCiso_kyr[, 2]

# Calculate the rate of change in ‰/kyr
rate_of_change <- -1 * diff(d13C) / diff(time_kyr)  # ‰/kyr
midpoints <- head(time_kyr, -1) + diff(time_kyr) / 2  # midpoints for plotting
rate_df <- data.frame(Time_kyr = midpoints, d13C_rate = rate_of_change)

# Define plotting data
ages = dCiso_kyr[, 1] / 1000
data = dCiso_kyr[, 2]
ages_2 = rate_df[, 1] / 1000
data_1 = rate_df[, 2]

# Plot settings
units = c("Age", "Epoch", "Period")
tick.scale = "myr"
boxes = "Age"
cex.age = 1
cex.ts = 1
cex.pt = 1
age.lim = c(445, 418)
data.lim = c(range(dCiso_kyr[, 2]))
ts.col = TRUE
ts.width = 0.3
vers = "ICS2015"
no.axis = FALSE
direction = "vertical"

# Load timescale
timescales <- NULL
data(timescales, envir = environment())
timescale <- timescales[[vers]]

tick.scale <- "myr"

# Validate direction
if (all(direction != c("horizontal", "vertical"))) {
  return(cat("direction must be either 'horizontal' or 'vertical', here set to 'horizontal'."))
  direction <- "horizontal"
}

# Prepare units
units <- tolower(units)
units <- paste(toupper(substring(units, 1, 1)), substring(units, 2), sep = "")
convert <- matrix(ncol = 2, nrow = 5)
convert[, 1] <- c("Eonothem", "Erathem", "System", "Series", "Stage")
convert[, 2] <- c("Eon", "Era", "Period", "Epoch", "Age")
for (c in 1:length(units)) {
  if (!is.na(convert[match(units[c], convert[, 1]), 2])) {
    units[c] <- convert[match(units[c], convert[, 1]), 2]
  }
}
label <- ""

if (any(units == "User") && missing(user.scale) == TRUE) {
  print("You need to specify a file for the argument user.scale, option for 'User' is removed")
  units <- subset(units, units != "User")
}

# Timescale setup
tscale_data <- matrix(ncol = 3, nrow = 6)
colnames(tscale_data) <- c("srt", "Depth", "size")
rownames(tscale_data) <- c("Eon", "Era", "Period", "Epoch", "Age", "User")
if (direction == "vertical") {
  tscale_data[, "srt"] <- c(90, 90, 90, 0, 0, 0)
} else {
  tscale_data[, "srt"] <- c(0, 0, 0, 90, 90, 90)
}
tscale_data[, "Depth"] <- c(0.5, 0.5, 0.5, 1, 1, 1)
tscale_data[, "size"] <- c(1, 1, 1, 0.8, 0.7, 0.5)
tscale <- timescale[order(timescale[, 1], decreasing = T), ]
units <- rownames(tscale_data)[sort(match(units, rownames(tscale_data)), decreasing = T)]

if (tick.scale == "myr") {
  scale.ticks <- 1
  scale.ages <- 10
  ticks <- seq(0, 4600, scale.ticks)
  time <- seq(0, 4600, scale.ages)
  tick.width <- c(1, 0.5, 0.5, 0.5, 0.5)
  tick.col <- c("black", "grey", "grey", "grey", "grey")
} else if (any(tick.scale == c("User", "Eon", "Era", "Period", "Epoch", "Age"))) {
  if (tick.scale == "User") {
    time <- user.scale
  } else {
    time <- subset(timescale, timescale[, "Type"] == tick.scale)
  }
  time <- unique(c(time[, 1], time[, 2]))
  ticks <- time
  tick.width <- 1
  tick.col <- "black"
} else if (is.numeric(tick.scale)) {
  ticks <- seq(0, 4600, tick.scale)
  time <- seq(0, 4600, tick.scale)
  tick.width <- 1
  tick.col <- "black"
}

# === PLOT LAYOUT ===
if (missing(age.lim)) {
  age.lim <- rev(range(ages))
}

panel.widths <- c(1, 0.5, 0.5, 0.5)
panel.starts <- cumsum(c(0, panel.widths)) / sum(panel.widths)
panel.ends <- panel.starts[-1]
panel.starts <- panel.starts[-length(panel.starts)]

# LEFT PANEL (Timescale)
par(fig = c(panel.starts[1], panel.ends[1], 0, 1))
par(mar = c(6, 2, 2, 0), mgp = c(1.5, 0.4, 0))
plot(data, ages, type = "n", xaxt = "n", yaxt = "n",
     bty = "n", ylab = "", xlab = "", xlim = data.lim,
     ylim = age.lim, main = "A", cex.lab = 1)
mtext("Age (Ma)", side = 2, line = -0.1, cex = 0.8, las = 0, srt = 90)

timescale.names <- timescale
timescale.names <- timescale.names[
  timescale.names[, "End"] < max(c(par()$usr[3], par()$usr[4])) &
    timescale.names[, "Start"] > min(c(par()$usr[3], par()$usr[4])),
]
timescale.names[timescale.names[, "End"] < min(c(par()$usr[3], par()$usr[4])), "End"] <- min(c(par()$usr[3], par()$usr[4]))
timescale.names[timescale.names[, "Start"] > max(c(par()$usr[3], par()$usr[4])), "Start"] <- max(c(par()$usr[3], par()$usr[4]))
timescale.names[, "Midpoint"] <- (timescale.names[, "Start"] + timescale.names[, "End"]) / 2
timescale.names[, "Range"] <- timescale.names[, "Start"] - timescale.names[, "End"]

for (t in 1:length(timescale.names[, 1])) {
  if (timescale.names[t, "Range"] < timescale[rownames(timescale.names)[t], "Range"] * 0.3) {
    timescale.names[t, "Name"] <- NA
  }
}

unit.depths <- tscale_data[units, "Depth"]
unit.depths <- c(unit.depths, 0.8)
unit.depths <- cumsum(unit.depths / sum(unit.depths))
unit.depths <- c(par()$usr[2], par()$usr[2] - (unit.depths * (par()$usr[2] - par()$usr[1])))

val <- unit.depths[length(unit.depths) - 1] - unit.depths[length(unit.depths)]

if (tick.scale != "n") {
  text((unit.depths[length(unit.depths)] + val * 0.3),
       time, time, cex = cex.age, srt = 0)
  segments((unit.depths[length(unit.depths) - 1]),
           ticks, (unit.depths[length(unit.depths)] + val * 0.75),
           ticks, lwd = tick.width)
}

for (t in 1:length(units)) {
  if (units[t] == "User") {
    tscale_n <- user.scale
  } else {
    tscale_n <- subset(tscale, tscale[, "Type"] == units[t])
  }

  if (ts.col == TRUE & units[t] != "User") {
    rect(unit.depths[t], tscale_n[, "End"], unit.depths[t + 1], tscale_n[, "Start"],
         col = rgb(tscale_n[, "Col_R"], tscale_n[, "Col_G"], tscale_n[, "Col_B"], maxColorValue = 255))
  } else {
    rect(unit.depths[t], tscale_n[, "Start"], unit.depths[t + 1], tscale_n[, "End"], col = "white")
  }

  if (units[t] == "User") {
    text(tscale_n[, "Midpoint"], (unit.depths[t] + unit.depths[t + 1]) / 2,
         tscale_n[, "Name"],
         cex = cex.ts * tscale_data[match(units[t], rownames(tscale_data)), "size"],
         srt = tscale_data[match(units[t], rownames(tscale_data)), "srt"])
  } else {
    text((unit.depths[t] + unit.depths[t + 1]) / 2,
         timescale.names[timescale.names["Type"] == units[t], "Midpoint"],
         timescale.names[timescale.names["Type"] == units[t], "Name"],
         cex = cex.ts * tscale_data[match(units[t], rownames(tscale_data)), "size"],
         srt = tscale_data[match(units[t], rownames(tscale_data)), "srt"])
  }
}

# MIDDLE PANEL (δ13C)
par(fig = c(panel.starts[2], panel.ends[2], 0, 1), new = TRUE)
par(mar = c(6, 0, 2, 0))
plot(data, ages, type = "n", yaxt = "n",
     ann = TRUE, bty = "n", xlab = "",
     ylim = age.lim, xlim = data.lim, main = "B", cex.lab = 0.8)
if (!missing(boxes)) {
  tscale_x <- if (boxes == "User") user.scale else subset(tscale, tscale[, "Type"] == boxes)
  rect(par()$usr[1], tscale_x[, "Start"], par()$usr[2],
       tscale_x[, "End"], col = c("grey90", "white"), border = NA)
}
lines(data, ages, ylab = "", cex = cex.pt)
if (!missing(d13C_tuned)) lines(d13C_tuned[, 2], (d13C_tuned[, 1] / 1000), col = "darkgreen", lwd = 2)

mtext(expression(atop(delta^13*C[carb], "(‰)")), 
      side = 1, line = 2.5, cex = 0.8)
# RIGHT PANEL (Rate of change)
par(fig = c(panel.starts[3], panel.ends[3], 0, 1), new = TRUE)
par(mar = c(6, 0, 2, 2))
plot(y = rate_df[, 1] / 1000, x = rate_df[, 2], type = "l", col = "white", ylim = age.lim,
     xlim = c(-0.04, 0.1), xlab = "", 
     ylab = "", bty = "n", yaxt = "n",
     main = "C", cex.lab = 1)
if (!missing(boxes)) {
  tscale_x <- if (boxes == "User") user.scale else subset(tscale, tscale[, "Type"] == boxes)
  rect(par()$usr[1], tscale_x[, "Start"], par()$usr[2],
       tscale_x[, "End"], col = c("grey90", "white"), border = NA)
}
if (!missing(d13C_tuned)) lines(d13C_tuned[, 3], (d13C_tuned[, 1] / 1000), col = "darkgreen", lwd = 2)
lines(y = rate_df[, 1] / 1000, x = rate_df[, 2], lwd = 1.2)
mtext(expression(atop(delta^13*C[carb], "rate of change (‰/kyr)")), 
      side = 1, line = 2.5, cex = 0.8)



# FOURTH PANEL (δ13C rate of change)
par(fig = c(panel.starts[4], panel.ends[4], 0, 1), new = TRUE)
par(mar = c(6, 0, 2, 1))
plot(d13C_tuned[, c(3, 1)], type = "l", main = "D",
     col = "darkgreen", xlab = "",
     yaxt = "n", yaxs = "i", ylab = "", bty = "n",  cex.lab = 1)
lines(d13C_tuned_diff_110[, c(2, 1)], col = "darkgreen", lwd = 2)

mtext(expression(atop(delta^13*C[carb], "rate of change (‰/kyr)")), 
      side = 1, line = 2.5, cex = 0.8)

# CONNECTING LINES
par(fig = c(0, 1, 0, 1), new = TRUE)
par(mar = c(0, 0, 0, 0))
plot(0, 0, type = "n", axes = FALSE, xlab = "", ylab = "", xlim = c(0, 1), ylim = c(0, 1))
segments(x1 = 0.76, y0 = 0.99, x0 = 0.84, y1 = 0.805, lwd = 2, lty = 3, col = "darkgrey")
segments(x0 = 0.76, y0 = 0.75, x1 = 0.84, y1 = 0.1, lwd = 2, lty = 3, col = "darkgrey")

Figure 11. The GTS 2020 its δ13Ccarb curve and the δ13Ccarb rate of change curve calculated from the δ13Ccarb curve, which is compared to the δ13Ccarb record and δ13Ccarb rate of change record from the Kosov Quarry section.A.Silurian timescale GTS 2020 (shifted up by 0.25 Myrs). B. Silurian δ13Ccarb curve from the GTS 2020 (shifted up by 0.25 Myrs) overlain by the δ13Ccarb curve of the Kosov Quarry section. C . Silurian δ13Ccarb rate of change curve calculated based on the δ13Ccarb curve from the GTS 2020 (shifted up by 0.25 Myrs) overlain by the δ13Ccarb rate of change curve based on the one of the Kosov Quarry section. D. The δ13Ccarb rate of change record (‰/kyr) and the 100-kyr eccentricity cycle extracted from said record.

4.3 Astronomical pacing the lag-1 sea-level curve

The lag-1 curve used as a sea-level proxy aligns with previous sea-level interpretations made for the record of the Kosov quarry section (Manda and Kříž, 2006; Frýda et al., 2020, 2021b) (see figure 10). The sea-level regression inferred from the Kosov record is consistent with global evidence for glacio-eustatic sea-level drawdown during the Lau Event, including sedimentary and isotopic signatures from Laurentia, Baltica, and Gondwana (Manda and Kříž, 2006; Jeppsson et al., 2007; Eriksson and Calner, 2008; Kiipli et al., 2010; Trotter et al., 2016; Frýda et al., 2021b). Our new results when placed ina global context underscored the gloabal synchronicity climatic perturbation assocaited with the Lau Biogeochemical Event and brings forth additaionl supports that a short-lived icehouse episode occured within an otherwise greenhouse like world of the late Silurian.

The 405-kyr eccentricity cycle extracted from the rate lag-1 record is generally anti-phased with the 405-kyr eccentricity cycle extracted from the induration record, indicating that sea level is highest during eccentricity maxima (Figure 10). The relationship between astronomical forcing and sea-level is much akin to that observed when sea-level was under a glacio-eustatic regime (Lourens and Hilgen, 1997; Li et al., 2018). The transition from relatively stable low-amplitude variability into pronounced eccentricity-paced oscillations during the Lau Event highlights the coupling between astronomical forcing, glacio-eustatic change, and the global carbon cycle during the Lau Event. The increased amplitude of sea-level variability indicates that astronomically paced glacio-eustatic fluctuations were amplified by the climatic changes associated with the Lau Biogeochemical Event that culmulated in the Mid-Ludfordian Glaciation (Frýda et al., 2021b).

5. Conclusions

The new astrochronology from the Kosov Quarry provides the first high-resolution temporal framework for the Lau Biogeochemical Event. Cyclostratigraphic analysis demonstrates that the Lau Biogeochemical Event started at 424.03 ± 0.55 Ma and lasted 0.87 ± 0.09 Myr, while the associated LKB Event was much shorter at 30 ± 10 kyr. Astronomical forcing is strongly imprinted across multiple proxies. The induration record is imprinted by astronomical cycles ranging from the 9-kyr half-precession to the 405-kyr eccentricity cycle. The δ¹³Ccarb rate-of-change record contains a strong imprint of the 100-kyr eccentricity cycle, whereas the lag-1 sea-level curve displays a moderate imprint by the 405-kyr eccentricity cycle. The δ¹³Ccarb rate-of-change curve further documents maximum values of 0.09 ‰/kyr during the onset of the Lau, highlighting the highly dynamic character of the Silurian carbon cycle. The imprint of the 100-kyr eccentricity cycle in the δ¹³Ccarb rate-of-change also indicates that this cycle acted as a pacemaker of carbon cycle variability and carbon burial during the Lau Event. The strong imprint of the 405-kyr eccentricity cycle in the lag-1 sea-level and the inferred phase relationship indicate that sea-level was subjected to an astronomically paced glacio-eustatic regime. Collectively, these findings demonstrate that the Lau Biogeochemical Event represents a climatically amplified eccentricity-paced perturbation of the Silurian carbon cycle. The astrochronologically constrained rate of δ¹³C change, coupled with coeval glacio-eustatic oscillations, highlights the sensitivity of the mid-Paleozoic Earth system to orbital forcing. These results suggest that even during intervals of high baseline warmth, astronomically paced feedbacks between climate dynamics, organic carbon burial, and ice-sheet growth could operate effectively, shaping both carbon cycling and sea-level evolution on eccentricity timescales.

Supplementary figures

Code
Ludlow_col <- geo_col(name = "Ludlow")
Pridoli_col <- geo_col(name = "Pridoli")
    
Top_record  <- max(kosov_time[,2])
bottom_record <- min(kosov_time[,2])
  
ylims <- rev(c(min(kosov_time[, 2])-5,max(kosov_time[,2])+5))
vert_lines <-   c(8.5,16,28,50,105,200,405)



induration_time <- linterp(induration_time,genplot=FALSE,verbose=FALSE)
induration_time_wt <- analyze_wavelet(data = induration_time,
                                      dj=1/200,upperPeriod = 1000,
                                      lowerPeriod = 5,omega_nr = 6)

{
layout.matrix <- matrix(c(rep(0,4),1,rep(0,4),seq(2,10,by=1)),
                        nrow = 2,
                        ncol = 9 ,
                        byrow = TRUE)

graphics::layout(mat = layout.matrix,
                 heights = c(0.25,1),
                 # Heights of the two rows
                 widths = c(rep(c(2,1,3,3,6,3,3,3),2)))

par(mar = c(0, 0.5, 1, 0.5))


wavelet <- induration_time_wt
xlim_vals = rev(c(min(wavelet$x), max(wavelet$x)))
ylim_vals = c(5,1000)
n.levels <- 100

color_brewer_Sel <-
  "grDevices::rainbow(n=n.levels, start = 0, end = 0.7)"
key.cols <- rev(eval(parse(text = color_brewer_Sel)))
power_max_mat.levels = quantile(wavelet$Power, probs = seq(
  from = 0,
  to = 1,
  length.out = n.levels + 1
))


periodlab <- "period (kyr)"
main = NULL

plot(
  wavelet$Period,
  wavelet$Power.avg,
  typ = "l",
  log = "x",
  xlim = ylim_vals,
  xaxt = 'n',
  xaxs = "i"
)

abline(v = vert_lines, col=adjustcolor("black", alpha.f=0.4), lwd=6, lty=1)


text(x=6.5,
     y=0.01,
  labels="E",
  cex =2
)


par(mar = c(4, 4, 0, 0))


plot(
  x = c(0, 1),
  y = c(max(kosov_time[, 1]) + 5, min(kosov_time[, 1]) -
          5),
  col = "white",
  xlab = "",
  ylab = "Age (Ma)",
  xaxt = "n",
  xaxs = "i",
  yaxs = "i",
  yaxt= "n",
  ylim = ylims,
)    

par(xpd = NA)
title(main = "A", line = 1, cex.main = 1.5)
par(xpd = FALSE) # reset

a <- round(c(max(kosov_time[, 2]), min(kosov_time[, 2]))/1000)

axis(2, at = rev(1000*seq(a[1],
                          a[2]-0.25, by = -0.25)),
     labels = rev(seq(a[1],
                      a[2]-0.25, by = -0.25)))

Hmisc::minor.tick(nx = 0, ny = 10,   # Ticks density
                  tick.ratio = 0.5) # Ticks size


polygon(
    y = c(
      bottom_record,
      bottom_record,
      base_pridoli,
      base_pridoli
    ),
    x = c(0, 1, 1, 0),
    col = Ludlow_col
  )

    polygon(
    y = c(
      base_pridoli,
      base_pridoli,
      Top_record,
      Top_record
    ),
    x = c(0, 1, 1, 0),
    col = Pridoli_col
  )

    text(
    labels = "Ludlow",cex=2,
    x = 0.5,
    srt = 270,
    y = (bottom_record + base_pridoli) / 2
  )

    text(
    labels = "Pridoli",cex=2,
    x = 0.5,
    srt = 270,
    y = (Top_record + base_pridoli) / 2
  )
 
   
par(mar = c(4, 0.5, 0, 0.5))

    
plot(
  x = c(0, 1),
  y = c(max(kosov_time[, 1]) + 5, min(kosov_time[, 1]) -
          5),
  col = "white",
  xlab = "",
  ylab = "",
  xaxt = "n",
  xaxs = "i",
  yaxs = "i",
  yaxt = "n",
  ylim = ylims
)    

par(xpd = NA)
title(main = "B", line = 1, cex.main = 1.5)
par(xpd = FALSE) # reset

polygon(
    y = c(
      base_LAU,
      base_LAU,
      base_S_zone,
      base_S_zone
    ),
    x = c(0, 1, 1, 0),
    col = "lightgrey"
  )
    text(
      labels = "R-zone",cex=1,
    x = 0.5,
    srt = 270,
    y = (base_LAU + base_S_zone) / 2
  )

polygon(
    y = c(
      base_S_zone,
      base_S_zone,
      base_F_zone,
      base_F_zone
    ),
    x = c(0, 1, 1, 0),
    col = "lightgrey"
  )
    text(
    labels = "S-zone",cex=2,
    x = 0.5,
    srt = 270,
    y = (base_S_zone + base_F_zone) / 2
  )

polygon(
    y = c(
      base_F_zone,
      base_F_zone,
      Top_F_zone,
      Top_F_zone
    ),
    x = c(0, 1, 1, 0),
    col = "lightgrey"
  )
    text(
    labels = "F-zone",cex=2,
    x = 0.5,
    srt = 270,
    y = (Top_F_zone + base_F_zone) / 2
  )

 plot(
  d13C_tuned_2[, 2],
  d13C_tuned_2[, 1],
  type = "l",
  ylim = ylims,
  xlab =  "δ13Ccarb",
  yaxt = "n",
  xaxs = "i",
  xlim = c(min(d13C_tuned_2[, 2]) - 0.15, max(d13C_tuned_2[, 2]) *
             1.1),
  yaxs = "i",col="lightgreen"
)

 par(xpd = NA)
title(main = "C", line = 1, cex.main = 1.5)
par(xpd = FALSE) # reset
 
 
polygon(
  x =
    c(
      min(d13C_tuned_2[, 2]) - 0.15,
      max(d13C_tuned_2[, 2]) * 1.1,
      max(d13C_tuned_2[, 2]) * 1.1,
      min(d13C_tuned_2[, 2]) - 0.15
    ),
  y = c(
    Top_F_zone,
    Top_F_zone,
    base_LAU,
    base_LAU
  ),
  col = rgb(0, 0, 0, 0.25)
)

 text(
    labels = "Lau δ13Ccarb excursion",
    x = 0.5,
    srt = 270,cex=2,
    y = (Top_F_zone + base_LAU) / 2
  )
 
 
abline(h=base_event,col="red",lty=3,lwd=3) 

    text(
    labels = "Onset \n LKB",
    x = 4,cex=2,
    srt = 0,
    y = (base_event + 5)
  ) 

lines(d13C_tuned_2[,c(2,1)],col="lightgreen",lwd=1)
lines(d13C_tuned[,c(2,1)],col="darkgreen",lwd=1)



plot(
  induration_time[, 2],
  induration_time[, 1],
  type = "l",
  ylim =ylims,
  yaxs = "i",
  yaxt = "n",
  xlab = "Induration"
)

par(xpd = NA)
title(main = "D", line = 1, cex.main = 1.5)
par(xpd = FALSE) # reset
image(
  y = wavelet$x,
  x = wavelet$axis.2,
  z = (wavelet$Power),
  col = key.cols,
  breaks = power_max_mat.levels,
  useRaster = TRUE,
  xlab = periodlab,
  ylab = "",
  #axes = FALSE,
  #yaxt = "n" ,
  xaxt = "n" ,
  yaxt = "n" ,
  main = main,
  ylim = ylims,
  xlim = log2(ylim_vals)
)

periodtck = 0.02
periodtcl = 0.5
main = NULL
lwd = 2
lwd.axis = 1
box(lwd = lwd.axis)

period.tick = unique(trunc(wavelet$axis.2))
period.tick <- period.tick[period.tick >= log2(ylim_vals[1])]
period.tick <- period.tick[period.tick <= log2(ylim_vals[2])]
nrs <- seq(period.tick[1], length(period.tick), by = 2)
period.tick <- nrs
period.tick[period.tick < log2(wavelet$Period[1])] = NA
period.tick = na.omit(period.tick)
period.tick.label = 2 ^ (period.tick)

axis(
  1,
  lwd = lwd.axis,
  at = period.tick,
  labels = NA,
  tck = periodtck,
  tcl = periodtcl
)


mtext(
  period.tick.label,
  side = 1,
  at = period.tick,
  las = 2,
  line = par()$mgp[2] - 0.5,
  font = par()$font.axis,
  cex = par()$cex.axis
)

abline(v = log2(vert_lines), col=adjustcolor("black", alpha.f=0.4), lwd=6, lty=1)
xlims <- range(induration_time[, 2])
plot(
  x = induration_filt_time_405[, 2],
  y = induration_filt_time_405[, 1],
  type = "l",
  ylim =ylims,xlim=xlims,
,
  yaxs = "i",
  yaxt = "n",
  xlab = "405-kyr ecc"
)
lines(induration_time[, 2],
  induration_time[, 1],col="grey")

par(xpd = NA)
title(main = "F", line = 1, cex.main = 1.5)
par(xpd = FALSE) # reset

lines(x = (induration_filt_time_110_hilb_405[, 2]
            -mean(induration_filt_time_110_hilb_405[, 2]))*2+mean(induration_filt_time_405[,2]), y = induration_filt_time_110_hilb_405[, 1], col =
        "red")

plot(
  x = induration_filt_time_110[, 2],
  y = induration_filt_time_110[, 1],
  type = "l",
  ylim =ylims,xlim=xlims,
,
  yaxs = "i",
  yaxt = "n",
  xlab = "100-kyr ecc")
lines(induration_time[, 2],
  induration_time[, 1],col="grey")

par(xpd = NA)
title(main = "G", line = 1, cex.main = 1.5)
par(xpd = FALSE) # reset

lines(
  induration_filt_time_110_hilb[, 2] + mean(induration_filt_time_110[, 2]),
  induration_filt_time_110_hilb[, 1],
  lwd = 1.5,col="red"
)



plot(
  x = induration_filt_time_prec[, 2],
  y = induration_filt_time_prec[, 1],
  type = "l",
  ylim =ylims,xlim=xlims,
,
  yaxs = "i",
  yaxt = "n",
  xlab = "Precession"
)
lines(induration_time[, 2],
  induration_time[, 1],col="grey")

par(xpd = NA)
title(main = "H", line = 1, cex.main = 1.5)
par(xpd = FALSE) # reset

lines(
  induration_filt_time_prec_hilb[, 2] + mean(induration_filt_time_prec[, 2]),
  induration_filt_time_prec_hilb[, 1],
  lwd = 1.5,col="red"
)


plot(
  x = induration_filt_time_obl[, 2],
  y = induration_filt_time_obl[, 1],
  type = "l",
  ylim =ylims,xlim=xlims,
,
  yaxs = "i",
  yaxt = "n",
  xlab = "Obliquity"
)
lines(induration_time[, 2],
  induration_time[, 1],col="grey")

par(xpd = NA)
title(main = "I", line = 1, cex.main = 1.5)
par(xpd = FALSE) # reset

lines(
  induration_filt_time_obl_hilb[, 2] + mean(induration_filt_time_obl[, 2]),
  induration_filt_time_obl_hilb[, 1],
  lwd = 1.5,col="red"
)
}

Figure SI 1. Induration proxy record in the time domain, including the wavelet scalogram of the induration record and astronomical cycles extracted from said record. A Stages. B. Event zone subdivision C. The δ13Ccarb record. D. The Induration record. E. Wavelet scalogram of the induration record with the average spectral power on top. The black vertical lines in the wavelet scalograms are durations of known astronomical cycles. From left to right, these cycles are the 8.5-kyr half precession, ~16-kyr precession, 28-kyr obliquity,50-kyr eccentricity, 100-kyr eccentricity, 200-kyr eccentricity and the 405-kyr eccentricity cycle. F. The black line is a 405-kyr eccentricity cycle extracted from the Induration record. The red line is the 405-kyr eccentricity cycle extracted from the Hilbert transform of the 100-kyr eccentricity cycle of the Induration record. G. The 100-kyr eccentricity cycle was extracted from the Induration record (black line) and the Hilbert transform of the 100-kyr eccentricity cycle (red line). H.) Obliquity cycle extracted from the Induration record (black line), and the Hilbert transform of the obliquity cycle (red line). I. The precession cycle extracted from the Induration record (black line) and the Hilbert transform of the precession cycle (red line).

6. References

Arts, M., Corradini, C., Pondrelli, M., Pas, D., and Da Silva, A.C., 2024, Age and orbital forcing in the upper Silurian Cellon section (Carnic Alps, Austria) uncovered using the WaverideR R package: Frontiers in Earth Science, v. 12, p. 1–24, doi:10.3389/feart.2024.1357751.
Cramer, B.D., and Jarvis, I., 2020, Carbon Isotope Stratigraphy: BV, 309–343 p., doi:10.1016/B978-0-12-824360-2.00011-5.
Cramer, B.D., Schmitz, M.D., Huff, W.D., and Bergström, S.M., 2015, High-precision u–pb zircon age constraints on the duration of rapid biogeochemical events during the ludlow epoch (Silurian period): Journal of the Geological Society, v. 172, p. 157–160, doi:10.1144/jgs2014-094.
Eriksson, M.J., and Calner, M., 2008, A sequence stratigraphical model for the Late Ludfordian (Silurian) of Gotland, Sweden: Implications for timing between changes in sea level, palaeoecology, and the global carbon cycle: Facies, v. 54, p. 253–276, doi:10.1007/s10347-007-0128-y.
Farhat, M., Auclair-Desrotour, P., Boué, G., and Laskar, J., 2022, The resonant tidal evolution of the Earth-Moon distance: Astronomy & Astrophysics, v. 665, p. L1.
Fatka, O., and Mergl, M., 2009, The “microcontinent” Perunica: Status and story 15 years after conception: Geological Society, London, Special Publications, v. 325, p. 65–101, doi:10.1144/SP325.4.
Frýda, J., Lehnert, O., Frýdová, B., Farkaš, J., and Kubajko, M., 2020, Carbon and sulfur cycling during the mid-Ludfordian anomaly and the linkage with the late Silurian Lau/Kozlowskii Bioevent: Palaeogeography, Palaeoclimatology, Palaeoecology, v. 564, p. 110152, doi:10.1016/j.palaeo.2020.110152.
Frýda, J., Lehnert, O., Joachimski, M.M., Männik, P., Kubajko, M., Mergl, M., Farkaš, J., and Frýdová, B., 2021b, The Mid-Ludfordian (late Silurian) Glaciation: A link with global changes in ocean chemistry and ecosystem overturns: Earth-Science Reviews, v. 220, p. 103652, doi:10.1016/j.earscirev.2021.103652.
Frýda, J., Lehnert, O., Joachimski, M.M., Männik, P., Kubajko, M., Mergl, M., Farkaš, J., and Frýdová, B., 2021a, The Mid-Ludfordian (late Silurian) Glaciation: A link with global changes in ocean chemistry and ecosystem overturns: Earth-Science Reviews, v. 220, p. 103652, doi:10.1016/j.earscirev.2021.103652.
Frýda, J., and Manda, Š., 2013, A long-lasting steady period of isotopically heavy carbon in the late Silurian ocean: Evolution of the &delta;13 record and its significance for an integrated &delta;13, graptolite and conodont stratigraphy: Bulletin of Geosciences, p. 463–482, doi:10.3140/bull.geosci.1436.
Gambacorta, G., Menichetti, E., Trincianti, E., and Torricelli, S., 2018, Orbital control on cyclical primary productivity and benthic anoxia: Astronomical tuning of the Telychian Stage (Early Silurian): Palaeogeography, Palaeoclimatology, Palaeoecology, v. 495, p. 152–162, doi:10.1016/j.palaeo.2018.01.003.
Hilgen, F., Zeeden, C., and Laskar, J., 2020, Paleoclimate records reveal elusive 200-kyr eccentricity cycle for the first time: Global and Planetary Change, v. 194, p. 103296, doi:10.1016/j.gloplacha.2020.103296.
Hinnov, L.A., 2000, New Perspectives on Orbitally Forced Stratigraphy: Annual Review of Earth and Planetary Sciences, v. 28, p. 419–475, doi:10.1177/0741713604268894.
HORNÝ, R., 1955, Base vrstev kopaninských eß 1 na jihozápadním okraji vulkanické facie (Kosov u Berouna): Věstník Ústředního ústavu geologického, v. 30, p. 81–86.
Jeppsson, L., Talent, J.A., Mawson, R., Simpson, A.J., Andrew, A.S., Calner, M., Whitford, D.J., Trotter, J.A., Sandström, O., and Caldon, H.J., 2007, High-resolution Late Silurian correlations between Gotland, Sweden, and the Broken River region, NE Australia: Lithologies, conodonts and isotopes: Palaeogeography, Palaeoclimatology, Palaeoecology, v. 245, p. 115–137, doi:10.1016/j.palaeo.2006.02.032.
Kiipli, T., Kiipli, E., and Kaljo, D., 2010, Silurian sea level variations estimated using SiO2/Al2O 3 and K2O/A12O3 ratios in the Priekule drill core section, latvia: Bollettino della Societa Paleontologica Italiana, v. 49, p. 55–63.
Kříž, J., 1992, Silurian field excursions: Prague Basin (Barrandian), Bohemia: National Museum of Wales, v. 13.
Kříž, J., 1991, The Silurian of the Prague Basin (Bohemia)–tectonic, eustatic and volcanic controls on facies and faunal development: Special Papers in Palaeontology, v. 44, p. 179–203.
Laskar, J., 2020, Astrochronology, in Geologic Time Scale 2020, Elsevier, v. 1774, p. 139–158, doi:10.1016/B978-0-12-824360-2.00004-8.
Laskar, J., Robutel, P., Joutel, F., Gastineau, M., Correia, a.C.M., and Levrard, B., 2004, A long-term numerical solution for the insolation quantities of the Earth: Astronomy and Astrophysics, v. 428, p. 261–285, doi:10.1051/0004-6361:20041335.
Lehnert, O., Frýda, J., Buggisch, W., Munnecke, A., Nützel, A., Křiž, J., and Manda, S., 2007, δ13C records across the late Silurian Lau event: New data from middle palaeo-latitudes of northern peri-Gondwana (Prague Basin, Czech Republic): Palaeogeography, Palaeoclimatology, Palaeoecology, v. 245, p. 227–244, doi:10.1016/j.palaeo.2006.02.022.
Li, M., Hinnov, L.A., Huang, C., and Ogg, J.G., 2018, Sedimentary noise and sea levels linked to land-ocean water exchange and obliquity forcing: Nature Communications, v. 9, doi:10.1038/s41467-018-03454-y.
Lourens, L.J., and Hilgen, F.J., 1997, Long-periodic variations in the earth’s obliquity and their relation to third-order eustatic cycles and late Neogene glaciations: Quaternary International, v. 40, p. 43–52, doi:10.1016/S1040-6182(96)00060-2.
Manda, Š., and Kříž, J., 2006, Environmental and biotic changes in subtropical isolated carbonate platforms during the Late Silurian Kozlowskii Event, Prague Basin: GFF, v. 128, p. 161–168.
Martinez, M., 2018, Mechanisms of Preservation of the Eccentricity and Longer-term Milankovitch Cycles in Detrital Supply and Carbonate Production in Hemipelagic Marl-Limestone Alternations: Elsevier Ltd, v. 3, 189–218 p., doi:10.1016/bs.sats.2018.08.002.
Melchin, M.J., Sadler, P.M., and Cramer, B.D., 2020, The Silurian Period (F. M. Gradstein, J. G. Ogg, M. D. Schmitz, & G. M. Ogg, Eds.): BV, 695–732 p., doi:10.1016/B978-0-12-824360-2.00021-8.
Meyers, S.R., 2019, Cyclostratigraphy and the problem of astrochronologic testing: Earth-Science Reviews, v. 190, p. 190–223, doi:10.1016/j.earscirev.2018.11.015.
Meyers, S.R., 2015, The evaluation of eccentricity-related amplitude modulation and bundling in paleoclimate data: An inverse approach for astrochronologic testing and time scale optimization: Paleoceanography, v. 30, p. 1625–1640, doi:10.1002/2015PA002850.
Mutterlose, J., and Ruffell, A., 1999, Milankovitch-scale palaeoclimate changes in pale-dark bedding rhythms from the Early Cretaceous (Hauterivian and Barremian) of eastern England and northern Germany: Palaeogeography, Palaeoclimatology, Palaeoecology, v. 154, p. 133–160, doi:10.1016/S0031-0182(99)00107-8.
Rossignol-Strick, M., 1985, MEDITERRANEAN QUATERNARY SAPROPELS, AN IMMEDIATE RESPONSE OF THE AFRICAN MONSOON TO VARIATION OF INSOLATION: Elsevier Science Publishers B.V, v. 49, p. 237–263, http://eesc.ldeo.columbia.edu/courses/w4937/Readings/Rossignol_Strick.1985.pdf.
Sachs, J.P., and Repeta, D.J., 1999, Oligotrophy and nitrogen fixation during eastern Mediterranean sapropel events: Science, v. 286, p. 2485–2488.
Scotese, C.R., 2021, An atlas of phanerozoic paleogeographic maps: The seas come in and the seas go out: Annual Review of Earth and Planetary Sciences, v. 49, p. 679–728, doi:10.1146/annurev-earth-081320-064052.
Štorch, P., 1995, Biotic crises and post-crisis recoveries recorded by Silurian planktonic graptolite faunas of the Barrandian area (Czech Republic): Geolines, v. 3, p. 59–70.
Tasáryová, Z., Schnabl, P., Čížková, K., Pruner, P., Janoušek, V., Rapprich, V., Štorch, P., Manda, Š., Frýda, J., and Trubač, J., 2014, Gorstian palaeoposition and geotectonic setting of Suchomasty Volcanic Centre (Silurian, Prague Basin, Teplá-Barrandian Unit, Bohemian Massif): Gff, v. 136, p. 262–265, doi:10.1080/11035897.2013.879735.
Trotter, J.A., Williams, I.S., Barnes, C.R., Männik, P., and Simpson, A.J., 2016, New conodont δ18O records of Silurian climate change: Implications for environmental and biological events: Palaeogeography, Palaeoclimatology, Palaeoecology, v. 443, p. 34–48, doi:10.1016/j.palaeo.2015.11.011.
Van Os, B., Lourens, L., Hilgen, F., De Lange, G., and Beaufort, L., 1994, The formation of Pliocene sapropels and carbonate cycles in the Mediterranean: Diagenesis, dilution, and productivity: Paleoceanography, v. 9, p. 601–617.
Waltham, D., 2015, Milankovitch Period Uncertainties and Their Impact On Cyclostratigraphy: Journal of Sedimentary Research, v. 85, p. 990–998, doi:10.2110/jsr.2015.66.
Wang, P., 2009, Global monsoon in a geological perspective: Chinese Science Bulletin, v. 54, p. 1113–1136.
Wu, Y., Malinverno, A., Meyers, S.R., and Hinnov, L.A., 2024, A 650-Myr history of Earth’s axial precession frequency and the evolution of the Earth-Moon system derived from cyclostratigraphy: Science Advances, v. 10, p. eado2412.